iOS 多线程有几种方式
- GCD
- NSOpeartion
- NSThread
- phread
多线程
GCD
-
dispatch_once_t
+ (TestModel *)shared { static TestModel *model; static dispatch_once_t once; dispatch_once(&once, ^{ model = [[TestModel alloc] init]; }); return model; }
-
dispatch_group
- (void)dispatchGroup { dispatch_queue_t queue = dispatch_queue_create("groupTest", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ NSLog(@"任务1 ready"); sleep(2); NSLog(@"任务1 完成"); }); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@"任务2 ready"); sleep(4); NSLog(@"任务2 完成"); dispatch_group_leave(group); }); dispatch_group_notify(group, queue, ^{ NSLog(@"group 任务完成"); }); }
- dispatch_group_notify 可以添加多次,并会多次调用
- 只要 group 中的任务没有完成,group 完成的监听就不会被调用,即使是后追加的任务
- notify 方法中第二个 queue 的参数决定了 callBack 将会在那个队列执行
-
dispatch_apply
多线程快速遍历本质是
dispatch_sync
与dispatch_group
关联的 api,因为该方法会等待内部所有操作都结束再返回,内部操作是否同步依赖传入的queue,外部必定是同步的。如果有需要,需将该方法放到一个异步的并行队列中如果传入多线程,输出的下标未必按照顺序执行
- (void)dispatchApply { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(10, queue, ^(size_t index) { NSLog(@"%@------%d", NSThread.currentThread, index); sleep(1); }); NSLog(@"111111"); } // "111111" 必定出现在最后
-
定时器
DispatchSourceTimer
不同于基于 Runloop 的
NSTimer
,DispatchSourceTimer
不会因为子线程没有正在运行的 Runloop 而失效,也不会有循环引用、计时不准(每次 runloop 循环才会检查定时器是否需要被执行)等问题。但有几点需要注意:- suspend 与 resume 一定要成对使用,否则会 crash
- timer 最好被持有,否则在 suspend 时可能 crash
var timer: DispatchSourceTimer? var index: Int = 0 func startTimer() { timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.global()) timer?.schedule(deadline: .now(), repeating: 0.1) timer?.setEventHandler(handler: { print("index: \(index)") index += 1 if index == 10 { cancelTimer() } }) timer?.resume() } private func cancelTimer() { timer?.cancel() timer = nil }
-
信号量
DispatchSemaphore
信号量类似于锁,信号量为 0 则阻塞线程,大于 0 则不会阻塞。因此可以通过改变信号量的值来控制是否阻塞线程
-
DispatchSemaphore(value: 0)
初始化 -
semaphore.signal()
// 信号量 +1 -
semaphore.wait()
在信号量大于 0 的前提下,信号量 -1,如果信号量本来为 0,则线程休眠,加入到等待这个信号的线程队列当中。当信号量大于 0 时,就会唤醒这个等待队列中靠前的线程,继续线程后面的代码且对信号量减 1,也就确保了信号量大于 0 才减 1,所以不存在信号量小于 0 的情况(除非在初始化时设置为负数,不过这样做应用程序会 crash)。
// 下载两个图片后执行操作 A // 1. 初始化信号量为 0 var seamphore = DispatchSemaphore(value: -0) // 2. 操作 A 前添加两个 wait 操作 seamphore.wait() seamphore.wait() // 3. 在每个下载结果回调中添加 seamphore.signal()
-
-
线程栅栏
dispatch_barrier
线程栅栏可以阻塞某个 queue(必须是自定义的并行 queue,如果是 global ,则不会有预期效果)中任务的执行直到 queue 中栅栏之前的任务执行完毕
- (void)dispatchBarrier { dispatch_queue_t queue = dispatch_queue_create("aaa", DISPATCH_QUEUE_CONCURRENT); for (int i = 1; i <= 3; i ++) { dispatch_async(queue, ^{ sleep(1); NSLog(@"%d 任务结束", i); }); } NSLog(@"栅栏前面"); dispatch_barrier_sync(queue, ^{ sleep(3); NSLog(@"栅栏结束"); }); NSLog(@"栅栏后面"); for (int i = 4; i <= 6; i ++) { dispatch_async(queue, ^{ sleep(1); NSLog(@"%d 任务结束", i); }); } NSLog(@"代码结束"); }
运行结果
dispatch_barrier_sync 运行结果 2020-07-24 20:17:09.905062+0800 ObjcTest[13685:969878] 栅栏前面 2020-07-24 20:17:10.907255+0800 ObjcTest[13685:969907] 2 任务结束 2020-07-24 20:17:10.907310+0800 ObjcTest[13685:969908] 1 任务结束 2020-07-24 20:17:10.907579+0800 ObjcTest[13685:969906] 3 任务结束 2020-07-24 20:17:13.907888+0800 ObjcTest[13685:969878] 栅栏结束 2020-07-24 20:17:13.908349+0800 ObjcTest[13685:969878] 栅栏后面 2020-07-24 20:17:13.908954+0800 ObjcTest[13685:969878] 代码结束 2020-07-24 20:17:14.913851+0800 ObjcTest[13685:969906] 4 任务结束 2020-07-24 20:17:14.914364+0800 ObjcTest[13685:969908] 5 任务结束 2020-07-24 20:17:14.914628+0800 ObjcTest[13685:969907] 6 任务结束 dispatch_barrier_async 运行结果 2020-07-24 20:18:16.955921+0800 ObjcTest[13691:970322] 栅栏前面 2020-07-24 20:18:16.956002+0800 ObjcTest[13691:970322] 栅栏后面 2020-07-24 20:18:16.956040+0800 ObjcTest[13691:970322] 代码结束 2020-07-24 20:18:17.961793+0800 ObjcTest[13691:970352] 1 任务结束 2020-07-24 20:18:17.962022+0800 ObjcTest[13691:970353] 2 任务结束 2020-07-24 20:18:17.963561+0800 ObjcTest[13691:970357] 3 任务结束 2020-07-24 20:18:20.967381+0800 ObjcTest[13691:970357] 栅栏结束 2020-07-24 20:18:21.974364+0800 ObjcTest[13691:970357] 4 任务结束 2020-07-24 20:18:21.974901+0800 ObjcTest[13691:970353] 5 任务结束 2020-07-24 20:18:21.975174+0800 ObjcTest[13691:970352] 6 任务结束
dispatch_barrier_sync
与dispatch_barrier_async
区别为- 同步栅栏会阻塞之后的普通代码的执行,异步栅栏则不会
应用线程栅栏的特性,可以更好的做一些线程同步。例如
任务 1、2、3结束后需要执行任务 4、5、6
如果用 dispatch_group ,则将任务 1、2、3 添加到 group 中,在 dispatch_group_notify 中执行任务 4、5、6
NSOperation
NSOperation 是苹果 GCD 面向对象的封装
优点:
添加操作间的依赖关系,控制执行顺序
可以方便的取消一个操作的执行
设定操作的优先级
使用 KVO 观察操作执行状态的更改
-
NSOperation 执行的方式
- 添加到 NSOperationQueue,系统会自动将 NSOperationQueue 中的 NSOperation 取出来,在线程中执行操作
- 如果不显式将 NSOperation 添加到 NSOperationQueue 中,也可以调用 operation.start() 方法,操作会在当前线程中执行
备注:使用
start
方法时,如果 NSBlockOperation 调用addExecutionBlock:
方法,添加额外的操作,包括blockOperationWithBlock
中的操作在内的这些操作可能在不同的线程中并发执行,具体由系统决定NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"1---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"2---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"3---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"4---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"5---%@", NSThread.currentThread); } }]; [op addExecutionBlock:^{ for (int i = 0; i < 3; i ++) { sleep(2); NSLog(@"6---%@", NSThread.currentThread); } }]; [op start];
2020-07-27 14:19:25.237520+0800 ObjcTest[19476:1527007] 1---<NSThread: 0x282870ac0>{number = 5, name = (null)} 2020-07-27 14:19:25.237532+0800 ObjcTest[19476:1526976] 2---<NSThread: 0x28283da80>{number = 1, name = main} 2020-07-27 14:19:27.239170+0800 ObjcTest[19476:1526976] 2---<NSThread: 0x28283da80>{number = 1, name = main} 2020-07-27 14:19:27.239164+0800 ObjcTest[19476:1527007] 1---<NSThread: 0x282870ac0>{number = 5, name = (null)} 2020-07-27 14:19:29.240627+0800 ObjcTest[19476:1526976] 2---<NSThread: 0x28283da80>{number = 1, name = main} 2020-07-27 14:19:29.240620+0800 ObjcTest[19476:1527007] 1---<NSThread: 0x282870ac0>{number = 5, name = (null)} 2020-07-27 14:19:31.242202+0800 ObjcTest[19476:1527007] 4---<NSThread: 0x282870ac0>{number = 5, name = (null)}
从结果中可以看出
blockOperationWithBlock
中的代码不是在主线程中执行的,并且系统一共开启了两个线程执行该 Operation -
NSOperationQueue 控制串行、并发
maxConcurrentOperationCount
属性可以控制一个队列中可以有多少个操作同时参与并发执行注意:这里的
maxConcurrentOperationCount
控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。 -
NSOperation 操作依赖
NSOperation 可以添加操作之间的依赖。因此我们可以很方便的控制操作之间的执行先后顺序。并且一个操作可以添加多个依赖。
例如我们有如下需求:A、B两个操作,A 执行完毕,B 才能执行操作。
// 1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.创建操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程 } }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程 } }]; // 3.添加依赖 [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2 // 4.添加操作到队列中 [queue addOperation:op1]; [queue addOperation:op2];
输出如下
2020-07-27 14:43:35.998300+0800 ObjcTest[19617:1533985] 1---<NSThread: 0x281496200>{number = 6, name = (null)} 2020-07-27 14:43:38.000611+0800 ObjcTest[19617:1533985] 1---<NSThread: 0x281496200>{number = 6, name = (null)} 2020-07-27 14:43:40.007131+0800 ObjcTest[19617:1533985] 2---<NSThread: 0x281496200>{number = 6, name = (null)} 2020-07-27 14:43:42.021944+0800 ObjcTest[19617:1533985] 2---<NSThread: 0x281496200>{number = 6, name = (null)}
可以看出,op1 先执行完毕后,op2 才开始执行
-
NSOperation 优先级
优先级只体现在两个时间点
- 依赖任务处理完成、队列对后续任务的调度
- 依赖队列从暂停转变为重新启动、后续任务的调度
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1--- 开始执行, %@", NSThread.currentThread); sleep(2); NSLog(@"1--- 执行完毕"); }]; op1.queuePriority = NSOperationQueuePriorityLow; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2--- 开始执行, %@", NSThread.currentThread); sleep(2); NSLog(@"2--- 执行完毕"); }]; op2.queuePriority = NSOperationQueuePriorityHigh; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"依赖--- 开始执行, %@", NSThread.currentThread); sleep(2); NSLog(@"依赖--- 执行完毕"); }]; [op1 addDependency:op3]; [op2 addDependency:op3]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperationCount = 1; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3];
结果如下
2020-07-27 15:13:05.623639+0800 ObjcTest[19750:1540177] 依赖--- 开始执行, <NSThread: 0x281561c00>{number = 5, name = (null)} 2020-07-27 15:13:07.629611+0800 ObjcTest[19750:1540177] 依赖--- 执行完毕 2020-07-27 15:13:07.630799+0800 ObjcTest[19750:1540177] 2--- 开始执行, <NSThread: 0x281561c00>{number = 5, name = (null)} 2020-07-27 15:13:09.631681+0800 ObjcTest[19750:1540177] 2--- 执行完毕 2020-07-27 15:13:09.632164+0800 ObjcTest[19750:1540176] 1--- 开始执行, <NSThread: 0x281561380>{number = 3, name = (null)} 2020-07-27 15:13:11.637458+0800 ObjcTest[19750:1540176] 1--- 执行完毕
-
NSOperationQueue 暂停和取消
注意:
- 这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕后不再执行新的操作
- 暂停和取消的区别在于:暂停之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,不再执行剩下的操作
NSThread
NSThread 在代码中偶尔会使用,例如 [NSThread currentThread]
-
创建、启动线程
- (void)threadTest { NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [thread start]; // 创建线程后自动启动线程 //[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; // 隐式创建并启动线程 //[self performSelectorInBackground:@selector(run) withObject:nil]; } - (void)run { NSLog(@"currentThread: %@", NSThread.currentThread); }
-
线程状态控制
+ (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti;// 线程进入阻塞状态 + (void)exit; // 线程 kill
-
线程间通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
-
与 runloop 关系
- (void)run { NSLog(@"currentThread: %@", NSThread.currentThread); [self performSelector:@selector(threadAfter) withObject:nil afterDelay:1]; } - (void)threadAfter { NSLog(@"1111111"); }
上述代码默认不会起作用,‘1111111’ 不会被打印出来。
performSelector: withObject: afterDelay:
方法默认会创建一个 timer 添加到当前 runloop 中,而子线程默认不开启 runloop,因此上述代码不起作用。
解决方法是在 performSelector 方法后面添加[[NSRunLoop currentRunLoop] run];
如果将 runloop 启动的代码放到前面,仍然不会起作用,原因是 runloop 启动后没有可执行的代码,会立刻退出,此时再添加 timer 也没有什么作用。
pthread
pthread 是一套通用的多线程 API,使用 C 语言编写,需要程序员自己管理线程的生命周期,使用难度较大,很少使用
-
pthread_create()
创建一个线程 -
pthread_exit()
终止当前线程 -
pthread_cancel()
中断另外一个线程的运行 -
pthread_join()
阻塞当前的线程,直到另外一个线程运行结束 -
pthread_attr_init()
初始化线程的属性 -
pthread_attr_setdetachstate()
设置脱离状态的属性(决定这个线程在终止时是否可以被结合) -
pthread_attr_getdetachstate()
获取脱离状态的属性 -
pthread_attr_destroy()
删除线程的属性 -
pthread_kill()
向线程发送一个信号
问题
- 子线程同时执行 ABC 三个同步任务,全部执行完毕后再在自线程执行三个同步任务 EDF,应该怎样做?
- 可以使用 GCD 的group 或者 NSOperation 的 依赖
- 可以使用 dispatch_barrier 稍简单一些
- 将问题 1 中的 ABC 三个任务改为异步任务如 AFN 网络请求,全部回调成功后进行数据整合,应该怎样做?
- 使用信号量
- 使用 GCD 的 group 也可以。
dispatch_group_enter
与dispatch_group_leave
搭配使用
- 线程的生命周期是怎样的?
可参考 线程的生命周期以及常驻线程