文章目录
前言
对于OS X
和iOS
的XNU内核线程切换时,会进行上下文切换,例如CPU
的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU
寄存器等信息,继续执行切换路径的CPU命令行。这被称为“上下文切换”。
使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列的执行多个线程一样。而且在具有多个CPU核的情况下,就不是“看上去像”了,而是真的提供了多个CPU核并行执行多个线程的技术。
多线程编程易发生各种问题,例如多个线程对资源的竞争,多个线程之间相互等待造成死锁,以及线程开辟消耗大量的内存等问题。
多线程的优点在于能够将复杂的处理独立出来,不会阻塞主线程这种线程的执行
GCD是基于XNU内核的线程技术,大大简化了多线程编程的源码。
Dispatch Queue
一般来说我们会这样异步执行一个任务
dispatch_async(queue,^{
//do your task
});
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
中线程数量。
例如:
dispatch_asyc(queue,blk0)
//.. 1 ~ 6
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
方法设置队列的优先级
//dipatch_queue_attr_make_with_qos_class
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.qosqueue", attr);
//dispatch_set_target_queue
dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.settargetqueue",NULL); //需要设置优先级的queue
dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级
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
就是串行执行。
例如下列代码:
- (void)set_tag_test {
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue3 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue3", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue4 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue4", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue5 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue5", DISPATCH_QUEUE_CONCURRENT);
//创建目标串行队列
dispatch_queue_t targetSerialQueue = dispatch_queue_create("com.gcd.setTargetQueue2.targetSerialQueue", NULL);
//设置执行阶层
dispatch_set_target_queue(serialQueue1, targetSerialQueue);
dispatch_set_target_queue(serialQueue2, targetSerialQueue);
dispatch_set_target_queue(serialQueue3, targetSerialQueue);
dispatch_set_target_queue(serialQueue4, targetSerialQueue);
dispatch_set_target_queue(serialQueue5, targetSerialQueue);
dispatch_async(serialQueue1, ^{
NSLog(@"%@,1",[NSThread currentThread]);
});
dispatch_async(serialQueue1, ^{
NSLog(@"%@,1 2",[NSThread currentThread]);
});
dispatch_async(serialQueue2, ^{
NSLog(@"%@,2",[NSThread currentThread]);
});
dispatch_async(serialQueue3, ^{
NSLog(@"%@,3",[NSThread currentThread]);
});
dispatch_async(serialQueue4, ^{
NSLog(@"%@,4",[NSThread currentThread]);
});
dispatch_async(serialQueue5, ^{
NSLog(@"%@,5",[NSThread currentThread]);
});
dispatch_async(targetSerialQueue, ^{
NSLog(@"%@,6",[NSThread currentThread]);
});
}
输出为:
<NSThread: 0x60000239b580>{number = 3, name = (null)},1
<NSThread: 0x60000239b580>{number = 3, name = (null)},1 2
<NSThread: 0x60000239b580>{number = 3, name = (null)},2
<NSThread: 0x60000239b580>{number = 3, name = (null)},3
<NSThread: 0x60000239b580>{number = 3, name = (null)},4
<NSThread: 0x60000239b580>{number = 3, name = (null)},5
<NSThread: 0x60000239b580>{number = 3, name = (null)},6
dispatch_after
- 延迟执行
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(),^{
//do task
});
dispatch_after
是延迟3s
加入Dispatch Queue,并不是马上执行,例如加入到主线程时,如果主线程卡段,这个延迟会大于3s
- 某一绝对时间执行
如果dispatch_after需要在2020/04/27 11:11:11
执行,那么dispatch_time_t
就可以使用dispatch_walltime
构造
dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];
struct timespec time;
dispatch_time_t milestone;
double second,subsecond;
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
milestone = dispatch_walltime(&time, 0);
GCD timer
void
dispatch_source_set_timer(dispatch_source_t source, //source timer
dispatch_time_t start, //开始时间
uint64_t interval, //执行间隔
uint64_t leeway); //允许的延迟时间 - 系统会尽量满足,但不一定保证在延迟时间内执行
一般来说我们定时器这样写:
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, (80 * NSEC_PER_MSEC)), (80 * NSEC_PER_MSEC), 0);
dispatch_source_set_event_handler(timer, ^{
//
});
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处理多个任务完成后,执行某个操作
- (void)dispatch_group_test {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0;");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1;");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2;");
});
//1. notify
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
//MRC
// dispatch_release(group);
}
控制台输出为:
2020-04-29 16:30:20.748658+0800 GCDTest[51629:2202347] blk0;
2020-04-29 16:30:20.748668+0800 GCDTest[51629:2202345] blk1;
2020-04-29 16:30:20.748687+0800 GCDTest[51629:2202346] blk2;
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部分替换为:
//2. wait 1 sec
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
// Group 中task全部执行完
}else {
// Group 中task还在执行中
}
-
我们可以通过将time参数设置为
DISPATCH_TIME_NOW
,然后在Runloop每次循环中来判断task是否执行完,以达到和dispatch_group_notify
同样的效果。 -
我们可以通过将time参数设置为
DISPATCH_TIME_FOREVER
来保证任务一定执行完成。等同于dispatch_group_notify
dispatch_barrier_async
对于访问数据库文件,我们要避免数据竞争,资源是互斥的。虽然我们可以使用Serial Dispatch Queue来解决这样的问题,但是为了高效处理,写入操作
确实不可与其他写入以及读取操作同时进行,但是多个读取操作时可以同时进行的,只需要保证写入是在多个读取之后进行。
- (void)dispatch_barrier_test {
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.barrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"read 0");
});
dispatch_async(queue, ^{
NSLog(@"read 1");
});
dispatch_async(queue, ^{
NSLog(@"read 2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"write");
});
dispatch_async(queue, ^{
NSLog(@"read 3");
});
dispatch_async(queue, ^{
NSLog(@"read 4");
});
dispatch_async(queue, ^{
NSLog(@"read 5");
});
}
这样就可以保证多次并发read
2020-04-29 17:45:00.149982+0800 GCDTest[56002:2263229] read 2
2020-04-29 17:45:00.149982+0800 GCDTest[56002:2263230] read 0
2020-04-29 17:45:00.149999+0800 GCDTest[56002:2263239] read 1
2020-04-29 17:45:00.150130+0800 GCDTest[56002:2263239] write
2020-04-29 17:45:00.150215+0800 GCDTest[56002:2263239] read 3
2020-04-29 17:45:00.150220+0800 GCDTest[56002:2263230] read 4
2020-04-29 17:45:00.150234+0800 GCDTest[56002:2263229] read 5
dispatch_apply
当我们需要将一个block
加入到queue
中,并多次执行时,使用dispatch_apply
,dispatch_apply
会阻塞当前线程,并等待queue执行完成。
- (void)dispatch_apply_test {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"done");
}
输出为:
2020-04-29 18:00:27.838731+0800 GCDTest[56107:2271775] 0
2020-04-29 18:00:27.838739+0800 GCDTest[56107:2271897] 1
2020-04-29 18:00:27.838739+0800 GCDTest[56107:2271893] 2
2020-04-29 18:00:27.838759+0800 GCDTest[56107:2271894] 3
2020-04-29 18:00:27.838858+0800 GCDTest[56107:2271897] 4
2020-04-29 18:00:27.838860+0800 GCDTest[56107:2271893] 5
2020-04-29 18:00:27.838861+0800 GCDTest[56107:2271894] 6
2020-04-29 18:00:27.838898+0800 GCDTest[56107:2271775] 7
2020-04-29 18:00:27.838941+0800 GCDTest[56107:2271897] 8
2020-04-29 18:00:27.838951+0800 GCDTest[56107:2271893] 9
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
提供的方法实现:
void* pthread_getspecific(pthread_key_t);
int pthread_setspecific(pthread_key_t , const void *);
GCD
中也提供了相关的技术
#import <Foundation/Foundation.h>
static const void * const key1 = &key1;
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_queue_t queue1 = dispatch_queue_create("com.ebebya.queue1", NULL);
dispatch_queue_set_specific(queue1, key1, (void *)[@"ebebya" UTF8String], NULL);
dispatch_async(queue1, ^{
void *value = dispatch_get_specific(key1);
NSString *str = [[NSString alloc] initWithBytes:value length:7 encoding:4];
NSLog(@"%@", str);
});
}
return 0;
}
但是是对于一个queue
队列的上下文键值对
,具体实现就不追究了。
demo链接:https://pan.baidu.com/s/1LDMFweqPKBbTFPzY2mhUxg
密码:tfvz