1.dispatch_suspend/dispatch_resume
这两个函数是用来暂停或继续队列运行的。当你想要往一个dispatch队列上添加多个任务,并且不想在全部任务添加完毕前执行任何一个任务,这个时候,你可以先让队列暂停运行,在你处理完后再让队列继续运行。
dispatch_suspend(queue); dispatch_resume(queue);
2.Dispatch Semaphore
当你需要对一小部分间隔时间较短的任务做并发控制的时候,Semaphore(信号量)会比串行队列或者dispatch_barrier_async更好用。
前面我们有提到,并发读取或更新数据时很容易造成数据冲突或者程序崩溃。你可以用串行队列或者dispatch_barrier_async函数来避免这种问题。但是有时需要在很短的时间间隔里做一些并发控制。比如,下面这个例子。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSMutableArray *array = [[NSMutableArray alloc] init]; for (int i = 0; i < 100000; ++i) { dispatch_async(queue, ^{ [array addObject:[NSNumber numberWithInt:i]]; }); }
Dispatch semaphore是一个带有一个计数器的信号量。这就是多线程编程中所谓的计数器信号量。信号量有点像一个交通信号标志,标志起来的时候你可以走,标准落下的时候你要停下来。Dispatch semaphore用计数器来模拟这种标志。计数器为0,队列暂停执行新任务并等待信号;当计数器超过0后,队列继续执行新任务,并减少计数器。
要创建一个dispatch semaphore,用dispatch_semaphore_create函数。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC); long result = dispatch_semaphore_wait(semaphore, time); if (result == 0) { /* * 信号量计数器大于等于1。 * 或者在指定的等待时间超时前,信号量计数器变成了大于等于1的数字。 * 计数器会自动减一 * * 在这里,可以安全地运行你的任务了。 */ } else { /* * 因为信号量计数器是0,就只能等待超时了。 * * 这里处理等待超时的情况。 */ }
现在来看看如何在前面那个例子中使用信号量。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); /* * 创建一个信号量 * * 将初始计数器设置为1, 使得一次只能有1个线程访问NSMutableArray对象。 */ dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); NSMutableArray *array = [[NSMutableArray alloc] init]; for (int i = 0; i < 100000; ++i) { dispatch_async(queue, ^{ /* * 等待信号量 * * 一直等待,直到信号量计数器大于等于1。 */ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); /* * 因为信号量计数器>=1, * dispatch_semaphore_wait函数停止等待,计数器自动-1,流程继续 * * 此例中,到这里的时候,计数器始终会变成0。 * 因为初始时为1,限定了一次只能有一个线程访问NSMutableArray对象。 * * 现在,在这里,你可以安全地更新数组了。 */ [array addObject:[NSNumber numberWithInt:i]]; /* * 任务完成后,要调用dispatch_semaphore_signal函数,使计数器+1 * 如果还有其他线程在等待信号量,第一个进入等待状态的线程得到通知后就可以开始了运行了。 */ dispatch_semaphore_signal(semaphore); )}; } /* * 如果不再需要这个信号量了,要将它释放掉. */ dispatch_release(semaphore);
当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,但如何在GCD中快速的控制并发呢?答案就是dispatch_semaphore,对经常做unix开发的人来讲,我所介绍的内容可能就显得非常入门级了,信号量在他们的多线程开发中再平常不过了。
在GCD中有三个函数是semaphore的操作,分别是:
dispatch_semaphore_create 创建一个semaphore
dispatch_semaphore_signal 发送一个信号
dispatch_semaphore_wait 等待信号
简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制。
dispatch_group_t group = dispatch_group_create(); dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (int i = 0; i < 100; i++) { dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_group_async(group, queue, ^{ NSLog(@"%i",i); sleep(2); dispatch_semaphore_signal(semaphore); }); } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group); dispatch_release(semaphore);
3.dispatch_once
dispatch_once函数用于确保一个任务在整个程序运行过程中,只会被执行一次。下面的代码是初始化一个对象的是的典型做法。
static int initialized = NO; if (initialized == NO) { /* * 初始化操作 */ initialized = YES; }
static dispatch_once_t pred; dispatch_once(&pred, ^{ /* * 初始化操作 */ });
读取一个大文件的时候,你可能会想到,如果将文件分成若干小块再用全局队列并发读取,这样会比正常情况下一次性读取整个文件快很多。对于目前的I/O硬件来说,并发读取可能确实会比单线程读取要快。要想获得更快的读取速度,可以使用Dispatch I/O和Dispatch Data。当你使用Dispatch I/0读写文件的时候,文件会被分成某个固定大小的文件块,你可以在一个全局队列上访问它们。
dispatch_async(queue, ^{/* 读取文件的 0 至 8191 字节。 */}); dispatch_async(queue, ^{/* 读取文件的 8192 至 16383 字节。 */}); dispatch_async(queue, ^{/* 读取文件的 16384 至 24575 字节 */}); dispatch_async(queue, ^{/* 读取文件的 24576 至 32767 字节 */}); dispatch_async(queue, ^{/* 读取文件的 32768 至 40959 字节 */}); dispatch_async(queue, ^{/* 读取文件的 40960 至 49151 字节 */}); dispatch_async(queue, ^{/* 读取文件的 49152 至 57343 字节 */}); dispatch_async(queue, ^{/* 读取文件的 57344 至 65535 字节 */});
pipe_q = dispatch_queue_create("PipeQ", NULL); pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){ close(fd); }); *out_fd = fdpair[1]; dispatch_io_set_low_water(pipe_channel, SIZE_MAX); dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){ if (err == 0) { size_t len = dispatch_data_get_size(pipedata); if (len > 0) { const char *bytes = NULL; char *encoded; dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len); encoded = asl_core_encode_buffer(bytes, len); asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded); free(encoded); _asl_send_message(NULL, merged_msg, -1, NULL); asl_msg_release(merged_msg); dispatch_release(md); } } if (done) { dispatch_semaphore_signal(sem); dispatch_release(pipe_channel); dispatch_release(pipe_q); } });
如果你想读取文件的时候速度快些,就试试用dispatch I/O吧。
参考文章:在CGD中快速实现多线程的并发控制 GCD使用详解