GCD对象及使用
一、概述
Objective-C语言下的GCD对象都被称为dispatch object对象,且都属于NSObject对象。dispatch object像Cocoa对象一样是引用计数的。因此,如果我们使用ARC,dispatch objects将被保留并自动释放,就像任何其他Objective-C对象一样。当使用MRC时,需要使用dispatch_retain和dispatch_release函数来保留和释放分派对象,不能使用Core Foundation框架的 retain/release函数。
看下dispatch_object的内部实现:
typedef union {
struct dispatch_object_s *_do;
struct dispatch_continuation_s *_dc;
struct dispatch_queue_s *_dq;
struct dispatch_queue_attr_s *_dqa;
struct dispatch_group_s *_dg;
struct dispatch_source_s *_ds;
struct dispatch_source_attr_s *_dsa;
struct dispatch_semaphore_s *_dsema;
struct dispatch_data_s *_ddata;
struct dispatch_io_s *_dchannel;
struct dispatch_operation_s *_doperation;
struct dispatch_disk_s *_ddisk;
} dispatch_object_t __attribute__((transparent_union));
可以看到dispatch_object_t可以表示的所有GCD对象类型(联合体类型就是干这事的)
二、派发队列Dispatch Queues
GCD提供并管理FIFO队列,应用程序可以将任务以块对象的形式提交给这些队列,提交给派发队列的任务在线程池(由系统完全管理的)中的线程上执行。
1、什么是队列?
队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中移除一个任务。
dispatch_queue按FIFO顺序获取提交给它的dispatch_block;
队列是等待执行的任务队列,即用来存放任务的队列。它衍生出来两个分类:并发和串行队列,一条线程上可以有多个队列。
串行队列(Serial Dispatch Queue):每次只有一个任务被执行,一个任务执行完毕后,再执行下一个任务(只开启一个线程);
为了避免多线程下的资源竞争问题,一般使用串行队列,同一时间这个队列只有一个线程来访问这个共享资源;
并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行(可以开启多个线程,并且同时执行任务);
并发队列 + 同步执行: 同步执行只能在当前线程中执行任务,不具备开启新线程的能力。所以 并发队列 + 同步执行 只会在当前线程里面同步执行这些任务,需要一个任务执行完毕后,再执行下一个任务。
并发队列 + 异步执行:对应着开启异步线程执行要执行的任务,就会同一时间又许多的任务被执行。
注意:并发队列的并发功能只有在异步(dispatch_async())函数下才有效。
两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
2、dispatch_queue内存结构
typedef struct dispatch_queue_s *dispatch_queue_t;
结构体struct dispatch_queue_s的内部成员:
#define DISPATCH_STRUCT_HEADER(x, y) \
const struct y *do_vtable; \
struct x *volatile do_next; \
unsigned int do_ref_cnt; \
unsigned int do_xref_cnt; \
unsigned int do_suspend_cnt; \
struct dispatch_queue_s *do_targetq; \
void *do_ctxt; \
void *do_finalizer;
#define DISPATCH_QUEUE_HEADER \
uint32_t volatile dq_running; \
uint32_t dq_width; \
struct dispatch_object_s *volatile dq_items_tail; \
struct dispatch_object_s *volatile dq_items_head; \
unsigned long dq_serialnum; \
dispatch_queue_t dq_specific_q;
struct dispatch_queue_s {
DISPATCH_STRUCT_HEADER(dispatch_queue_s, dispatch_queue_vtable_s);
DISPATCH_QUEUE_HEADER;
char dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE]; // must be last
char _dq_pad[DISPATCH_QUEUE_CACHELINE_PAD]; // for static queues only
};
3、系统提供的main_queue 与 global_queue
//获取主队列(与主线程关联的串行队列)
dispatch_queue_t dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}
//声明主队列 _dispatch_main_q
struct dispatch_queue_s _dispatch_main_q;
//主队列:结构体成员的赋值
struct dispatch_queue_s _dispatch_main_q = {
#if !DISPATCH_USE_RESOLVERS
.do_vtable = &_dispatch_queue_vtable,
.do_targetq = &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],
#endif
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.dq_label = "com.apple.main-thread",
.dq_running = 1,
.dq_width = 1,
.dq_serialnum = 1,
};
/*获取全局并发队列
long identifier :使用此队列执行的任务的优先级
unsigned long flags :为将来使用而保留的标志,这个参数指定0
*/
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
主队列main_queue:主队列由系统自动创建,并与应用程序的主线程相关联。
全局并发队列global_queue:是由系统定义的全局并发队列。
全局并发队列global_queue有四个优先级,通过XNU内核管理global_queue的线程,global_queue的执行优先级作为线程的执行优先级使用。在向global_queue追加任务时,应选择与任务内容对应优先级的global_queue。
但是通过XNU内核用于global_queue的线程并不能保证实时性,因此执行优先级只是大致的判断。
队列优先级 值描述
DISPATCH_QUEUE_PRIORITY_HIGH 最高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台
4、创建派发队列
//dispatch_queue 是一个轻量级对象,应用程序将块任务提交给该对象,以供后续执行
dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
函数有两个参数:
const char *_Nullable label队列标识,用于在调试(如工具、示例、堆栈快照和崩溃报告)。推荐使用反向dns命名样式如 "com.demo.gcd.taskA"。这个参数是可选的,可以为空。
dispatch_queue_attr_t _Nullable attr:队列类型,如串行队列DISPATCH_QUEUE_SERIAL(按FIFO顺序执行)、并发队列DISPATCH_QUEUE_CONCURRENT
5、队列性能分析
不论使用dispatch_queue_create()函数生成的并发队列或者使用dispatch_get_global_queue()获取的并发队列,在使用dispatch_async()函数执行任务时,都可以并发处理多个任务,但并发执行的任务数量取决于当前系统的状态。
iOS 或者 OS X 基于dispatch_queue中的任务数量、CPU核数以及CPU负荷等当前系统的状态来决定并发队列中并发执行的任务数量。iOS 和 OS X 的核心XNU内核决定应当使用的线程数,并且只生成所需的线程执行处理。当任务执行结束、应当执行的任务减少时,XNU 内核会结束不再需要的线程。XNU内核只需要针对并发队列便可完美的管理并执行多个处理任务的线程。
一个串行队列需要开辟一条线程来处理队里中的任务,那么也就是说假如在短时间内大量生成多个串行队列,那么就需要系统生产多个线程去处理对应的串行队列中的任务。而此时就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能。而对于并发队列来说,不管生成多少个并发队列,由于 XNU 内核只使用有效管理的线程,因此不会发生串行队列的那些问题。
我们分别测试下大量串行队列处理任务 与 大量并发队列处理任务的性能消耗:
串行队列的性能消耗:
- (void)dispatch_serial_morequeue_Method
{
dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"开始执行task%zu === %@",index,NSThread.currentThread);
[NSThread sleepForTimeInterval:10];//模拟耗时任务
NSLog(@"结束执行task%zu=== %@",index,NSThread.currentThread);
});
});
}
利用dispatch_apply()函数并发迭代了1000次,创建了 1000 条串行队列,每条串行队列都要执行一个耗时任务。理论上来说,系统需要创建 1000 条线程来处理这 1000 个串行队列中的任务,但是实际又如何呢?
我们运行程序,观看打印结果,发现线程并没有创建了1000个,而是在重复利用已经存在的线程,最大为283。
并发队列的性能消耗:
dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"开始执行task%zu === %@",index,NSThread.currentThread);
[NSThread sleepForTimeInterval:10];//模拟耗时任务
NSLog(@"结束执行task%zu=== %@",index,NSThread.currentThread);
});
});
利用dispatch_apply()函数并发迭代了1000次,创建了 1000 条并发队列,每条并发队列都要执行一个耗时任务。XNU 内核使用有效管理的线程来处理这 1000 个并发队列中的任务,我们运行程序,观看打印结果,
发现线程最大数为:开始执行task91 === {number = 65, name = (null)}
上述两种测试对比发现:XNU内核仅仅使用了60多条线程来处理1000个耗时任务,相对于比起串行队列的283多条线程,其对系统性能的消耗要远远优于串行队列。
三、派发任务
任务在GCD中是放在dispatch_block或者dispatch_function中执行的;
typedef void (^dispatch_block_t)(void);
typedef void (*dispatch_function_t)(void *_Nullable);
执行任务有两种方式:同步执行dispatch_sync和异步执行dispatch_async:
//同步执行
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
//异步执行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
1、同步执行与异步执行
dispatch_sync()将任务添加到派发队列中,由系统调度执行。该语句会堵塞当前线程(当然任务也在当前线程上执行),直到任务执行完毕才会接着向下执行。
dispatch_async()将任务添加到派发队列中,由系统调度执行,立即返回。
两个函数的参数都相同:
dispatch_queue_t queue 接收任务块的队列,系统将保留该队列,直到块运行完成为止。这个参数不能为空。
dispatch_block_t block 提交给派发队列的任务块,这个函数代表调用者执行Block_copy和Block_release。这个参数不能为空。
1.1、同步执行dispatch_sync()
//创建一个并发队列
dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
});
NSLog(@"------- 此处为taskA与taskB分界线 -------");
dispatch_sync(queue, ^{
NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
});
1.2、异步执行dispatch_async()
dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
});
NSLog(@"------- 此处为taskA与taskB分界线 -------");
dispatch_async(queue, ^{
NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
});
1.3 同步执行与异步执行的区别
主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
2.1 dispatch_function_t
前面提到的任务均隐式使用块dispatch_block来封装,下面演示使用dispatch_function函数封装任务,且使用dispatch_async_f()函数执行任务dispatch_function函数。
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
函数有三个参数:
第一个参数queue:添加任务到指定的分派队列,这个参数不能为空。
第二个参数void *_Nullable context:传递一个任意类型的参数至dispatch_function_t work
注意:此处void *为C语言的不确定类型,类似于Objective-C 中的id类型。
第三个参数dispatch_function_t work:dispatch_function_t是一个函数指针,指向应用程序定义的函数,我们可以在这个函数中处理耗时操作,如读写数据、下载文件等;typedef void (*dispatch_function_t)(void *_Nullable);
eg:
void dispatch_async_function_test(void *_Nullable context)
{
id object = (__bridge id)context;
NSLog(@"处理开始 : %@ == %@",object,NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"处理结束 : %@ == %@",object,NSThread.currentThread);
}
void dispatch_async_method()
{
dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_SERIAL);
dispatch_async_f(queue, @"这是一个任意类型的指针", dispatch_async_function_test);
}
3.1 dispatch_block显式封装任务及对其使用
dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);
void dispatch_block_perform(dispatch_block_flags_t flags,DISPATCH_NOESCAPE dispatch_block_t block);
long dispatch_block_wait(dispatch_block_t block, dispatch_time_t timeout);
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue, dispatch_block_t notification_block);
void dispatch_block_cancel(dispatch_block_t block);
使用函数dispatch_block_create()创建一个分派块,该函数有两个参数:
第一个参数dispatch_block_flags_t flags:标记执行考虑什么情况
第二个参数dispatch_block_t block:执行任务的块
返回一个新的dispatch_block或者返回NULL;
eg:
dispatch_block_t taskA = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"开始执行taskA === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行taskA === %@",NSThread.currentThread);
});
执行任务dispatch_block_perform()
eg:
dispatch_block_perform(DISPATCH_BLOCK_DETACHED, taskA);
等待任务执行dispatch_block_wait()
以同步方式等待,直到指定分派块的执行完成,或者直到指定的超时过去。它有两个参数:
第一个参数dispatch_block_t block:等待的调度块
第二个参数dispatch_time_t timeout:指定的超时时间
返回值为0表示在超时时间内执行完;非0表示超时完成。
eg:下面方法创建了三个任务块,将任务A添加到并行队列后,等待执行完(注意执行超时时,并没有对任务A做任何处理)
void dispatch_block_wait_method()
{
dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t taskA = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"开始执行taskA === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行taskA === %@",NSThread.currentThread);
});
dispatch_block_t taskB = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"开始执行taskB === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行taskB === %@",NSThread.currentThread);
});
dispatch_block_t taskC = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"开始执行taskC === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行taskC === %@",NSThread.currentThread);
});
dispatch_async(queue, taskA);
NSLog(@"执行 dispatch_block_wait");
long value = dispatch_block_wait(taskA, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)));
NSLog(@"dispatch_block_wait === %ld",value);
if (value == 0)
{
NSLog(@"执行成功");
dispatch_async(queue, taskB);
}
else
{
NSLog(@"执行超时");
dispatch_async(queue, taskC);
}
}
监听任务完成dispatch_block_notify()
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue, dispatch_block_t notification_block);
当block执行完时,将notification_block添加到queue中调度执行。
三个参数:
第一个参数dispatch_block_t block:被监听的任务块dispatch_block;
第三个参数dispatch_block_t notification_block:被监听块对象完成时要提交的通知块。
第二个参数dispatch_queue_t queue:通知块被执行的目标队列;
eg:
void dispatch_block_notify_method()
{
dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t taskA = dispatch_block_create(DISPATCH_BLOCK_ASSIGN_CURRENT, ^{
NSLog(@"开始执行taskA === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行taskA === %@",NSThread.currentThread);
});
dispatch_block_t taskB = dispatch_block_create(DISPATCH_BLOCK_ASSIGN_CURRENT, ^{
NSLog(@"开始执行taskB === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行taskB === %@",NSThread.currentThread);
});
dispatch_block_notify(taskA, queue, taskB);
dispatch_async(queue, taskA);
}
取消任务dispatch_block_cancel(),下面代码创建了taskA、taskB,调用dispatch_after()延迟取消taskA,调用dispatch_block_cancel()取消taskB。
结果发现,不能取消已启动的任务,只能取消没开始的任务(NSOperation的取消也是只设置状态,无法真正取消)。
void dispatch_block_cancel_method()
{
dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t taskA = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"开始执行taskA === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行taskA === %@",NSThread.currentThread);
});
dispatch_block_t taskB = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"开始执行taskB === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:2];//模拟耗时任务
NSLog(@"结束执行taskB === %@",NSThread.currentThread);
});
dispatch_async(queue, taskA);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_block_cancel(taskA);
});
dispatch_block_cancel(taskB);
dispatch_async(queue, taskB);
}
四、队列&任务
1、异步执行 + 并发队列
dispatch_async()以异步方式添加任务到指定的队列中,它不会阻塞当前的线程,可以继续执行任务;会开启多个线程,使多个任务同时执行。
2、异步执行 + 串行队列
dispatch_async()异步添加任务到指定的队列中,它不会阻塞当前的线程,可以继续执行任务;串行队列只开启一个线程,串行队列中的多个任务,每次只有一个任务被执行,任务一个接一个按顺序执行
3、异步执行 + 主队列
dispatch_async()以异步方式添加任务到指定的队列中,它不会阻塞当前的线程,可以继续执行任务;主队列是串行队列;串行队列中的多个任务,每次只有一个任务被执行,任务一个接一个按顺序执行,并且都在主线程上执行。
4、同步执行 + 并发队列
dispatch_sync()以同步方式添加任务到指定的队列中,在添加的任务执行结束之前,会阻塞当前的线程一直等待,直到队列里面的任务完成之后再继续执行;在当前线程上执行。
5、同步执行 + 串行队列
dispatch_sync()同步添加任务到指定的队列中,在添加的任务执行结束之前,会阻塞当前的线程一直等待,直到队列里面的任务完成之后再继续执行;在当前线程上执行。
6、同步执行 + 主队列
GCD死锁,我们将调用dispatch_sync()函数所在语句块叫做任务0,dispatch_sync()block参数叫做任务1,任务0等待任务1的完成,而任务1必须在任务0执行完后才能从队列中取出执行,这样就造成了互等。
五、队列操作:暂停/继续
1、暂停/继续
当追加大量任务块到dispatch_queue时,在追加任务块的过程中,有时希望不执行已追加的任务块。这时,只要挂起dispatch_queue即可。当可以执行时再恢复。
//挂起指定的dispatch_queue
void dispatch_suspend(dispatch_object_t object);
//恢复指定的dispatch_queue
void dispatch_resume(dispatch_object_t object);
这些函数对已经执行的任务块没有影响。挂起后,追加到dispatch_queue中但尚未执行的任务块在此之后停止。而恢复则使得这些处理能够继续执行。
eg:dispatch_suspend() 并不会暂停派发队列dispatch_queue中正在执行的任务,暂停的是队列中还未启动的任务。
void dispatch_suspend_method()
{
dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"开始执行taskA === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行taskA === %@",NSThread.currentThread);
});
dispatch_async(queue, ^{
NSLog(@"开始执行taskB === %@",NSThread.currentThread);
[NSThread sleepForTimeInterval:3];//模拟耗时任务
NSLog(@"结束执行taskB === %@",NSThread.currentThread);
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_suspend(queue);
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_resume(queue);
});
}
六、信号量dispatch_semaphore
dispatch_semaphore 的核心是 dispatch_semaphore_t类型的信号量,dispatch_semaphore 使用计数来实现该功能,计数为 0 时等待,计数为1或者大于 1 时,减去1而不等待。
如何在 dispatch_queue中控制线程的最大并发数?可以利用 GCD 的 dispatch_semaphore_t达到控制线程的最大并发数的目的!信号量最大值设为1,可以做条件依赖。
eg: //创建并发队列
dispatch_queue_t queue = dispatch_queue_create("current", DISPATCH_QUEUE_CONCURRENT);
//创建信号量,并设置最大值,最大值就是最大并发数
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_async(queue, ^{
//信号量大于等于1时,信号量减1,程序向下执行;信号量等于0时,就一直等待,直到信号量大于0;反正减1小于0就等待,知道减1大于等于0;
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务A, %@", [NSThread currentThread]);
sleep(1);
dispatch_semaphore_signal(semaphore);// 当线程任务执行完成之后,发送一个信号,信号量加1;
});
dispatch_async(queue, ^{
//信号量大于等于1时,信号量减1,程序向下执行;信号量等于0时,就一直等待,直到信号量大于0;反正减1小于0就等待,知道减1大于等于0;
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务B, %@", [NSThread currentThread]);
sleep(1);
dispatch_semaphore_signal(semaphore);// 当线程任务执行完成之后,发送一个信号,信号量加1;
});
dispatch_async(queue, ^{
//信号量大于等于1时,信号量减1,程序向下执行;信号量等于0时,就一直等待,直到信号量大于0;反正减1小于0就等待,知道减1大于等于0;
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务C, %@", [NSThread currentThread]);
sleep(1);
dispatch_semaphore_signal(semaphore);// 当线程任务执行完成之后,发送一个信号,信号量加1;
});
dispatch_async(queue, ^{
//信号量大于等于1时,信号量减1,程序向下执行;信号量等于0时,就一直等待,直到信号量大于0;反正减1小于0就等待,知道减1大于等于0;
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务D, %@", [NSThread currentThread]);
sleep(1);
dispatch_semaphore_signal(semaphore);// 当线程任务执行完成之后,发送一个信号,信号量加1;
});
七、dispatch_group
如果你想在队列中的所有任务都执行完毕后做一些事,dispatch_group可以帮你实现。
ispatch_group相关API:
dispatch_group_t dispatch_group_create(void);
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_notify_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
1、dispatch_group_t dispatch_group_create(void)
dispatch_group_t dispatch_group_create(void)
{
return (dispatch_group_t)dispatch_semaphore_create(LONG_MAX);
}
可以看出dispatch_group_t其本质就是一个值为LONG_MAX的信号量dispatch_semaphore_t。
2、void dispatch_group_enter(dispatch_group_t group) void dispatch_group_leave(dispatch_group_t group)
eg:
//创建并发队列
dispatch_queue_t queue = dispatch_queue_create("current", DISPATCH_QUEUE_CONCURRENT);
//创建派发任务组
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);//标志着一个任务追加到 group
dispatch_async(queue, ^{
NSLog(@"执行:task%d ======= %@",1,NSThread.currentThread);
dispatch_group_leave(group);//标志着一个任务离开了 group
});
dispatch_group_enter(group);//标志着一个任务追加到 group
dispatch_async(queue, ^{
NSLog(@"执行:task%d ======= %@",2,NSThread.currentThread);
dispatch_group_leave(group);//标志着一个任务离开了 group
});
dispatch_group_enter(group);//标志着一个任务追加到 group
dispatch_async(queue, ^{
NSLog(@"执行:task%d ======= %@",3,NSThread.currentThread);
dispatch_group_leave(group);//标志着一个任务离开了 group
});
dispatch_group_notify(group, queue, ^{
NSLog(@"over");
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_group_enter(group);//标志着一个任务追加到 group
NSLog(@"执行:task%d ======= %@",4,NSThread.currentThread);
dispatch_group_leave(group);//标志着一个任务离开了 group
});
何时从dispatch_group_notify第二个参数取出任务执行,取决于何时恢复LONG_MAX。
3、void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
第一个参数dispatch_group_t group任务组:
第二个参数dispatch_queue_t queue派发队列:
第三个参数dispatch_block_t block处理任务的块:
eg:
//创建并发队列
dispatch_queue_t queue = dispatch_queue_create("current", DISPATCH_QUEUE_CONCURRENT);
//创建派发任务组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"执行:task%d ======= %@",1,NSThread.currentThread);
});
dispatch_group_async(group, queue, ^{
NSLog(@"执行:task%d ======= %@",2,NSThread.currentThread);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"over");
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_group_async(group, queue, ^{
NSLog(@"执行:task%d ======= %@", 3, NSThread.currentThread);
});
});
可以看到:dispatch_group_async()函数的使用与dispatch_group_enter()、dispatch_group_leave()和dispatch_async()三个函数一起使用的效果完全一样;
4、dispatch_group_wait()
dispatch_group_wait()函数堵塞当前线程,直到该语句调用前添加到group的所有任务都执行完或超时
八、dispatch_after延迟执行
1、dispatch_time_t
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
第一个参数dispatch_time_t when:表示从什么时间开始,一般直接传 DISPATCH_TIME_NOW 表示从现在开始;
第二个参数int64_t delta:表示多长时间,delta的单位是纳秒,所以不能直接传 int 或 float, 需要写成这种形式 (int64_t)3 * NSEC_PER_SEC;
缩写 全拼 汉译
MSEC millisecond 毫秒
NSEC nanoseconds 纳秒
USEC microsecond 微秒
SEC second 秒
PER prep 每一
宏 值 含义
NSEC_PER_SEC 1000000000ull 每秒有10亿纳秒
NSEC_PER_MSEC 1000000ull 每毫秒有100万纳秒
USEC_PER_SEC 1000000ull 每秒有100万微秒
NSEC_PER_USEC 1000ull 每微秒有1000纳秒
2、dispatch_after
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
第一个参数dispatch_time_t when:指定将任务追加到队列中的时间, 不是在指定时间之后开始处理任务,而是在指定时间之后将任务追加到队列中。
第二个参数dispatch_queue_t queue:提交任务的队列,该队列由系统保留,直到任务运行到完成为止,这个参数不能为空。
第三个参数dispatch_block_t block:任务块,这个参数不能为空。