文章目录
前言
Dispatch Queue
Queue Create
Queue Release
MRC
ARC
Global Queue
如何设置QOS级别
Dispatch Set Target
dispatch_after
GCD timer
Dispatch Group
dispatch_group_notify
dispatch_group_wait
dispatch_barrier_async
dispatch_apply
dispatch_resume/dispatch_suspend
dispatch TLS
前言
对于OS X和iOS的XNU内核线程切换时,会进行上下文切换,例如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令行。这被称为“上下文切换”。
使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列的执行多个线程一样。而且在具有多个CPU核的情况下,就不是“看上去像”了,而是真的提供了多个CPU核并行执行多个线程的技术。
多线程编程易发生各种问题,例如多个线程对资源的竞争,多个线程之间相互等待造成死锁,以及线程开辟消耗大量的内存等问题。
多线程的优点在于能够将复杂的处理独立出来,不会阻塞主线程这种线程的执行
GCD是基于XNU内核的线程技术,大大简化了多线程编程的源码。
Dispatch Queue
一般来说我们会这样异步执行一个任务
1 dispatch_async(queue,^{
2 //do your task
3 });
queue代表一个队列,在GCD中存在两种队列形式
Serial Dispatch Queue 串行队列
Concurrent Dispatch Queue 并行队列
两种队列的在于,Serial处理task是根据队列先进先出顺序执行,会保证上一个task执行完再执行下一个,而Concurrent则是并发执行,不会等上一个task执行完
虽然Concurrent Dispatch Queue不用等待处理结束,可以并发执行多个task,但是其并发线程的数量却是取决于当前系统的状态。根据如下:
iOS和OS X基于Dispatch Queue中的处理数
CPU核数以及CPU负荷等当前系统的状态
来决定Concurrent Dispatch Queue中线程数量。
例如:
1 dispatch_asyc(queue,blk0)
2 //.. 1 ~ 6
3 dispatch_asyc(queue,blk7)
8个task可能根据当前系统的状态,会如下执行:
Queue Create
我们可以通过GCD的API来生成Dispatch Queue
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create(“com.example.gcd.MySerialDispatchQueue”,DISPATCH_QUEUE_SERIAL)
值得注意的是Serial Dispatch Queue的生成,当创建一个Serial Dispatch Queue,系统就默认只生成并使用一个线程。如果生成了2000个Serial Dispatch Queue那么就创建2000个线程,这对内存资源消耗是很大的。而Concurrent Dispatch Queue就没这个问题,他会根据系统状态来管理并发线程数量,所以虽然我们可以使用创建多个Serial Dispatch Queue来达到并发的目的,但是并不是GCD的初衷,我们应该使用Concurrent Dispatch Queue来处理并发任务
同样我们可以创建 Concurrent Dispatch Queue
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create(“com.example.gcd.MyConcurrentDispatchQueue”,DISPATCH_QUEUE_CONCURRENT)
即第二个参数决定了Queue的类型是 Serial 还是 Concurrent
Queue Release
MRC
对于Dispatch Queue,Queue必须创建后由程序员自己释放,因为Dispatch Queue并没有像block那样具有作为Object-C对象来处理的技术。
dispatch_release(myConcurrentDispatchQueue)
但是,Dispatch Queue同样有引用计数管理。意味着你可以
dispatch_retain(myConcurrentDispatchQueue)
例如下面代码
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create(“com.example.gcd.MyConcurrentDispatchQueue”,DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentDispatchQueue, ^{
//do your task
});
dispatch_release(myConcurrentDispatchQueue);
既然dispatch_release释放掉了,为什么任务还能执行,这也是因为block对queue进行了引用,就相当于调用了dispatch_retain
ARC
对于ARC模式下,你不需要手动调用dispatch_release(myConcurrentDispatchQueue);,ARC已经允许将Dispatch Queue作为对象加入ARC内存管理。
Global Queue
我们可以获取系统为我们提供的标准Global Queue,
在iOS 7之前,系统提供了4种优先级,以调度优先级区分
名称 执行优先级
DISPATCH_QUEUE_PRIORITY_HIGH 高(*高优先)
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认
DISPATCH_QUEUE_PRIORITY_LOW 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台
在iOS 8之后,系统提供了5种优先级,以服务质量划分的
名称 执行优先级
QOS_CLASS_USER_INTERATCTIVE 高(*高优先)
QOS_CLASS_USER_INITIATED 用户交互(不要使用耗时操作)
QOS_CLASS_DEFAULT 默认
QOS_CLASS_UTILITY 使用工具(用了做耗时操作)
QOS_CLASS_BACKGROUND 后台执行
QOS_CLASS_UNSPECIFIED 没有指定优先级
对于QOS_CLASS_UTILITY我们一般进行耗时操作,例如数据I/O,系统对其进行了优化,使得能够减少App的电量消耗。
如何设置QOS级别
dipatch_queue_attr_make_with_qos_class 或dispatch_set_target_queue方法设置队列的优先级
1 |
//dipatch_queue_attr_make_with_qos_class |
|
|
|
2 |
|
|
|
|
3 |
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1); |
|
|
|
4 |
|
|
|
|
5 |
dispatch_queue_t queue = dispatch_queue_create(“com.starming.gcddemo.qosqueue”, attr); |
|
|
|
6 |
|
|
|
|
7 |
|
|
|
|
8 |
|
|
|
|
9 |
//dispatch_set_target_queue |
|
|
|
10 |
|
|
|
|
11 |
dispatch_queue_t queue = dispatch_queue_create(“com.starming.gcddemo.settargetqueue”,NULL); //需要设置优先级的queue |
|
|
|
12 |
|
|
|
|
13 |
dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级 |
|
|
|
14 |
|
|
|
|
15 |
dispatch_set_target_queue(queue, referQueue); //设置queue和referQueue的优先级一样 |
|
|
|
|
|
|
|
|
Dispatch Set Target
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t _Nullable queue);
dispatch_set_target_queue能够设置queue的执行阶层,无论是Concurrent Queue还是Serial Queue,一旦设置target为Serial Queue就是串行执行。
例如下列代码:
1 |
– (void)set_tag_test { |
2 |
dispatch_queue_t serialQueue1 = dispatch_queue_create(“com.gcd.setTargetQueue2.serialQueue1”, DISPATCH_QUEUE_CONCURRENT); |
3 |
dispatch_queue_t serialQueue2 = dispatch_queue_create(“com.gcd.setTargetQueue2.serialQueue2”, DISPATCH_QUEUE_CONCURRENT); |
4 |
dispatch_queue_t serialQueue3 = dispatch_queue_create(“com.gcd.setTargetQueue2.serialQueue3”, DISPATCH_QUEUE_CONCURRENT); |
5 |
dispatch_queue_t serialQueue4 = dispatch_queue_create(“com.gcd.setTargetQueue2.serialQueue4”, DISPATCH_QUEUE_CONCURRENT); |
6 |
dispatch_queue_t serialQueue5 = dispatch_queue_create(“com.gcd.setTargetQueue2.serialQueue5”, DISPATCH_QUEUE_CONCURRENT); |
7 |
|
8 |
|
9 |
|
10 |
//创建目标串行队列 |
11 |
dispatch_queue_t targetSerialQueue = dispatch_queue_create(“com.gcd.setTargetQueue2.targetSerialQueue”, NULL); |
12 |
|
13 |
//设置执行阶层 |
14 |
dispatch_set_target_queue(serialQueue1, targetSerialQueue); |
15 |
dispatch_set_target_queue(serialQueue2, targetSerialQueue); |
16 |
dispatch_set_target_queue(serialQueue3, targetSerialQueue); |
17 |
dispatch_set_target_queue(serialQueue4, targetSerialQueue); |
18 |
dispatch_set_target_queue(serialQueue5, targetSerialQueue); |
19 |
|
20 |
dispatch_async(serialQueue1, ^{ |
21 |
NSLog(@”%@,1″,[NSThread currentThread]); |
22 |
}); |
23 |
dispatch_async(serialQueue1, ^{ |
24 |
NSLog(@”%@,1 2″,[NSThread currentThread]); |
25 |
}); |
26 |
dispatch_async(serialQueue2, ^{ |
27 |
NSLog(@”%@,2″,[NSThread currentThread]); |
28 |
}); |
29 |
dispatch_async(serialQueue3, ^{ |
30 |
NSLog(@”%@,3″,[NSThread currentThread]); |
31 |
}); |
32 |
dispatch_async(serialQueue4, ^{ |
33 |
NSLog(@”%@,4″,[NSThread currentThread]); |
34 |
}); |
35 |
dispatch_async(serialQueue5, ^{ |
36 |
NSLog(@”%@,5″,[NSThread currentThread]); |
37 |
}); |
38 |
dispatch_async(targetSerialQueue, ^{ |
39 |
NSLog(@”%@,6″,[NSThread currentThread]); |
40 |
}); |
41 |
} |
输出为:
1 |
<NSThread: 0x60000239b580>{number = 3, name = (null)},1 |
2 |
<NSThread: 0x60000239b580>{number = 3, name = (null)},1 2 |
3 |
<NSThread: 0x60000239b580>{number = 3, name = (null)},2 |
4 |
<NSThread: 0x60000239b580>{number = 3, name = (null)},3 |
5 |
<NSThread: 0x60000239b580>{number = 3, name = (null)},4 |
6 |
<NSThread: 0x60000239b580>{number = 3, name = (null)},5 |
7 |
<NSThread: 0x60000239b580>{number = 3, name = (null)},6 |
dispatch_after
延迟执行
1 |
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC); |
2 |
dispatch_after(time, dispatch_get_main_queue(),^{ |
3 |
//do task |
4 |
}); |
dispatch_after是延迟3s加入Dispatch Queue,并不是马上执行,例如加入到主线程时,如果主线程卡段,这个延迟会大于3s
某一*对时间执行
如果dispatch_after需要在2020/04/27 11:11:11执行,那么dispatch_time_t就可以使用dispatch_walltime构造
1 dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
1 |
NSTimeInterval interval = [[NSDate date] timeIntervalSince1970]; |
2 |
struct timespec time; |
3 |
dispatch_time_t milestone; |
4 |
double second,subsecond; |
5 |
subsecond = modf(interval, &second); |
6 |
time.tv_sec = second; |
7 |
time.tv_nsec = subsecond * NSEC_PER_SEC; |
8 |
milestone = dispatch_walltime(&time, 0); |
GCD timer
1 void
2 dispatch_source_set_timer(dispatch_source_t source, //source timer
3 dispatch_time_t start, //开始时间
4 uint64_t interval, //执行间隔
5 uint64_t leeway); //允许的延迟时间 – 系统会尽量满足,但不一定保证在延迟时间内执行
一般来说我们定时器这样写:
1 |
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); |
2 |
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, (80 * NSEC_PER_MSEC)), (80 * NSEC_PER_MSEC), 0); |
3 |
dispatch_source_set_event_handler(timer, ^{ |
4 |
// |
5 |
}); |
6 |
dispatch_resume(timer); |
注意dispatch_source是遵循ARC的,要强引用保证其不被释放。
为什么GCD定时器相比NSTimer定时器更准?
NSTimer是CFRunLoopTimerRef的toll-free bridged的,可以直接转换,当某一次执行被延后,错过了时间点,那么NSTimer会选择不执行此次回调,等到下次执行。所以相比来说,GCD定时器更为准确。
此外 GCD定时器无法保证每次的执行同时在一个线程中,就算是Serial队列也不行,而NSTimer是加入到线程的runloop中,所以必定是同一个线程,当我们需要单线程执行时,需要注意这点。
Dispatch Group
dispatch_group_notify
我们一般用dispatch_group处理多个任务完成后,执行某个操作
1 |
– (void)dispatch_group_test { |
2 |
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); |
3 |
dispatch_group_t group = dispatch_group_create(); |
4 |
dispatch_group_async(group, queue, ^{ |
5 |
NSLog(@”blk0;”); |
6 |
}); |
7 |
dispatch_group_async(group, queue, ^{ |
8 |
NSLog(@”blk1;”); |
9 |
}); |
10 |
dispatch_group_async(group, queue, ^{ |
11 |
NSLog(@”blk2;”); |
12 |
}); |
13 |
|
14 |
//1. notify |
15 |
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ |
16 |
NSLog(@”done”); |
17 |
}); |
18 |
|
19 |
//MRC |
20 |
// dispatch_release(group); |
21 |
} |
控制台输出为:
1 |
2020-04-29 16:30:20.748658+0800 GCDTest[51629:2202347] blk0; |
2 |
2020-04-29 16:30:20.748668+0800 GCDTest[51629:2202345] blk1; |
3 |
2020-04-29 16:30:20.748687+0800 GCDTest[51629:2202346] blk2; |
4 |
2020-04-29 16:30:20.761685+0800 GCDTest[51629:2201079] done |
追加到Dispatch Group的block会对Dispatch Group进行dispatch_retain并持有,在block 执行完之后释放Dispatch Group,所以一旦Dispatch Group使用结束,意味着block都不保持其引用了,可以放心释放。
dispatch_group_wait
判断指定时间后,task是否执行完,使用dispatch_group_wait
将上面1部分替换为:
1 |
|
2 |
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC); |
3 |
long result = dispatch_group_wait(group, time); |
4 |
|
5 |
if (result == 0) { |
6 |
// Group 中task全部执行完 |
7 |
}else { |
8 |
// Group 中task还在执行中 |
9 |
} |
我们可以通过将time参数设置为DISPATCH_TIME_NOW,然后在Runloop每次循环中来判断task是否执行完,以达到和dispatch_group_notify同样的效果。
我们可以通过将time参数设置为DISPATCH_TIME_FOREVER来保证任务一定执行完成。等同于dispatch_group_notify
dispatch_barrier_async
对于访问数据库文件,我们要避免数据竞争,资源是互斥的。虽然我们可以使用Serial Dispatch Queue来解决这样的问题,但是为了高效处理,写入操作确实不可与其他写入以及读取操作同时进行,但是多个读取操作时可以同时进行的,只需要保证写入是在多个读取之后进行。
1 |
– (void)dispatch_barrier_test { |
2 |
dispatch_queue_t queue = dispatch_queue_create(“com.example.gcd.barrier”, DISPATCH_QUEUE_CONCURRENT); |
3 |
dispatch_async(queue, ^{ |
4 |
NSLog(@”read 0″); |
5 |
}); |
6 |
dispatch_async(queue, ^{ |
7 |
NSLog(@”read 1″); |
8 |
}); |
9 |
dispatch_async(queue, ^{ |
10 |
NSLog(@”read 2″); |
11 |
}); |
12 |
dispatch_barrier_async(queue, ^{ |
13 |
NSLog(@”write”); |
14 |
}); |
15 |
dispatch_async(queue, ^{ |
16 |
NSLog(@”read 3″); |
17 |
}); |
18 |
dispatch_async(queue, ^{ |
19 |
NSLog(@”read 4″); |
20 |
}); |
21 |
dispatch_async(queue, ^{ |
22 |
NSLog(@”read 5″); |
23 |
}); |
24 |
} |
这样就可以保证多次并发read
1 |
2020-04-29 17:45:00.149982+0800 GCDTest[56002:2263229] read 2 |
2 |
2020-04-29 17:45:00.149982+0800 GCDTest[56002:2263230] read 0 |
3 |
2020-04-29 17:45:00.149999+0800 GCDTest[56002:2263239] read 1 |
4 |
2020-04-29 17:45:00.150130+0800 GCDTest[56002:2263239] write |
5 |
2020-04-29 17:45:00.150215+0800 GCDTest[56002:2263239] read 3 |
6 |
2020-04-29 17:45:00.150220+0800 GCDTest[56002:2263230] read 4 |
7 |
2020-04-29 17:45:00.150234+0800 GCDTest[56002:2263229] read 5 |
dispatch_apply
当我们需要将一个block加入到queue中,并多次执行时,使用dispatch_apply,dispatch_apply会阻塞当前线程,并等待queue执行完成。
1 |
– (void)dispatch_apply_test { |
2 |
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
3 |
dispatch_apply(10, queue, ^(size_t index) { |
4 |
NSLog(@”%zu”,index); |
5 |
}); |
6 |
NSLog(@”done”); |
7 |
} |
输出为:
1 |
2020-04-29 18:00:27.838731+0800 GCDTest[56107:2271775] 0 |
2 |
2020-04-29 18:00:27.838739+0800 GCDTest[56107:2271897] 1 |
3 |
2020-04-29 18:00:27.838739+0800 GCDTest[56107:2271893] 2 |
4 |
2020-04-29 18:00:27.838759+0800 GCDTest[56107:2271894] 3 |
5 |
2020-04-29 18:00:27.838858+0800 GCDTest[56107:2271897] 4 |
6 |
2020-04-29 18:00:27.838860+0800 GCDTest[56107:2271893] 5 |
7 |
2020-04-29 18:00:27.838861+0800 GCDTest[56107:2271894] 6 |
8 |
2020-04-29 18:00:27.838898+0800 GCDTest[56107:2271775] 7 |
9 |
2020-04-29 18:00:27.838941+0800 GCDTest[56107:2271897] 8 |
10 |
2020-04-29 18:00:27.838951+0800 GCDTest[56107:2271893] 9 |
11 |
2020-04-29 18:00:27.839616+0800 GCDTest[56107:2271775] done |
dispatch_resume/dispatch_suspend
当多个task在Dispatch Queue中执行时,有时不想再继续执行了,可以使用dispatch_suspend将队列挂起。再使用dispatch_resume恢复,值得注意的是,并不能取消已经开始的任务。
对于想取消的线程,并能监听线程的各种状态,可以使用NSOperation和NSOperationQueue,还能够设置*大并发量。
NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。
可以添加任务依赖,方便控制执行顺序
可以设定操作执行的优先级
任务执行状态控制:isReady,isExecuting,isFinished,isCancelled
如果只是重写NSOperation的main方法,由底层控制变更任务执行及完成状态,以及任务退出
如果重写了NSOperation的start方法,自行控制任务状态
系统通过KVO的方式移除isFinished==YES的NSOperation
可以设置*大并发量
参考链接:https://www.jianshu.com/p/cbf759bdfd0f
dispatch TLS
线程TLS技术,Thread Local Storage(TLS)线程局部存储,目的很简单,将一块内存作为某个线程专有的存储,以key-value的形式进行读写,比如在非arm架构下,使用pthread提供的方法实现:
1 void* pthread_getspecific(pthread_key_t);
2 int pthread_setspecific(pthread_key_t , const void *);
GCD中也提供了相关的技术
1 |
#import <Foundation/Foundation.h> |
2 |
|
3 |
static const void * const key1 = &key1; |
4 |
|
5 |
int main(int argc, const char * argv[]) { |
6 |
@autoreleasepool { |
7 |
dispatch_queue_t queue1 = dispatch_queue_create(“com.ebebya.queue1”, NULL); |
8 |
dispatch_queue_set_specific(queue1, key1, (void *)[@”ebebya” UTF8String], NULL); |
9 |
|
10 |
dispatch_async(queue1, ^{ |
11 |
void *value = dispatch_get_specific(key1); |
12 |
NSString *str = [[NSString alloc] initWithBytes:value length:7 encoding:4]; |
13 |
NSLog(@”%@”, str); |
14 |
}); |
15 |
} |
16 |
return 0; |
17 |
} |
但是是对于一个queue队列的上下文键值对,具体实现就不追究了。