GCD(III)
GCD 线程间的通信
在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。
1 // GCD 线程间的通信 2 gcd.communication() 3 4 print("<-------------------------线程结束-------------------------->") 5 6 7 //线程间通信 8 - (void)communication { 9 // 获取全局并发队列 10 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 11 // 获取主队列 12 dispatch_queue_t mainQueue = dispatch_get_main_queue(); 13 14 dispatch_async(queue, ^{ 15 // 异步追加任务 16 for (int i = 0; i < 2; ++i) { 17 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 18 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 19 } 20 21 // 回到主线程 22 dispatch_async(mainQueue, ^{ 23 // 追加在主线程中执行的任务 24 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 25 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 26 }); 27 }); 28 }
打印结果:
1 <-------------------------线程结束--------------------------> 2 2018-03-29 10:31:07.472967+0800 StruggleSwift[1128:21810] 1---<NSThread: 0x604000270a80>{number = 6, name = (null)} 3 2018-03-29 10:31:11.877147+0800 StruggleSwift[1128:21810] 1---<NSThread: 0x604000270a80>{number = 6, name = (null)} 4 2018-03-29 10:31:16.845082+0800 StruggleSwift[1128:18897] 2---<NSThread: 0x60c000262100>{number = 1, name = main}
结论:可以看到在其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作。
GCD API的其他方法
GCD 栅栏方法:dispatch_barrier_async
栅栏
一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async
方法在两个操作组间形成栅栏。dispatch_barrier_async
函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async
函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。具体如下图所示:1 // GCD 栅栏方法 2 gcd.barrier() 3 print("<-------------------------线程结束-------------------------->") 4 5 6 /栅栏方法 dispatch_barrier_async 7 - (void)barrier{ 8 dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT); 9 10 dispatch_async(queue, ^{ 11 // 追加任务1 12 for (int i = 0; i < 2; ++i) { 13 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 14 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 15 } 16 }); 17 dispatch_async(queue, ^{ 18 // 追加任务2 19 for (int i = 0; i < 2; ++i) { 20 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 21 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 22 } 23 }); 24 25 dispatch_barrier_async(queue, ^{ 26 // 追加任务 barrier 27 for (int i = 0; i < 2; ++i) { 28 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 29 NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程 30 } 31 }); 32 33 dispatch_async(queue, ^{ 34 // 追加任务3 35 for (int i = 0; i < 2; ++i) { 36 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 37 NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程 38 } 39 }); 40 dispatch_async(queue, ^{ 41 // 追加任务4 42 for (int i = 0; i < 2; ++i) { 43 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 44 NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程 45 } 46 }); 47 48 }
打印结果:
1 <-------------------------线程结束--------------------------> 2 2018-03-29 13:39:10.556316+0800 StruggleSwift[3411:185315] 2---<NSThread: 0x608000667a00>{number = 28, name = (null)} 3 2018-03-29 13:39:10.556325+0800 StruggleSwift[3411:186063] 1---<NSThread: 0x600000275900>{number = 29, name = (null)} 4 2018-03-29 13:39:12.557738+0800 StruggleSwift[3411:185315] 2---<NSThread: 0x608000667a00>{number = 28, name = (null)} 5 2018-03-29 13:39:12.557738+0800 StruggleSwift[3411:186063] 1---<NSThread: 0x600000275900>{number = 29, name = (null)} 6 2018-03-29 13:39:14.563020+0800 StruggleSwift[3411:186063] barrier---<NSThread: 0x600000275900>{number = 29, name = (null)} 7 2018-03-29 13:39:16.563415+0800 StruggleSwift[3411:186063] barrier---<NSThread: 0x600000275900>{number = 29, name = (null)} 8 2018-03-29 13:39:18.568870+0800 StruggleSwift[3411:186063] 3---<NSThread: 0x600000275900>{number = 29, name = (null)} 9 2018-03-29 13:39:18.568988+0800 StruggleSwift[3411:186167] 4---<NSThread: 0x60400007a940>{number = 30, name = (null)} 10 2018-03-29 13:39:20.570592+0800 StruggleSwift[3411:186063] 3---<NSThread: 0x600000275900>{number = 29, name = (null)} 11 2018-03-29 13:39:20.570592+0800 StruggleSwift[3411:186167] 4---<NSThread: 0x60400007a940>{number = 30, name = (null)}
结论:在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。
GCD 延时执行方法:dispatch_after
在指定时间(例如3秒)之后执行某个任务。可以用 GCD 的dispatch_after
函数来实现。需要注意的是:dispatch_after
函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after
函数是很有效的。
1 // 延时执行 2 gcd.after() 3 4 5 print("<-------------------------线程结束-------------------------->") 6 7 8 //GCD 延时执行方法:dispatch_after 9 - (void)after { 10 NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 11 NSLog(@"asyncMain---begin"); 12 13 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 14 // 2.0秒后异步追加任务代码到主队列,并开始执行 15 NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程 16 }); 17 }
打印结果:
1 2018-03-29 23:36:53.737909+0800 StruggleSwift[1132:25333] currentThread---<NSThread: 0x608000260a00>{number = 1, name = main} 2 2018-03-29 23:36:56.680732+0800 StruggleSwift[1132:25333] asyncMain---begin 3 <-------------------------线程结束--------------------------> 4 2018-03-29 23:37:09.704708+0800 StruggleSwift[1132:25333] after---<NSThread: 0x608000260a00>{number = 1, name = main}
GCD 一次性代码(只执行一次):dispatch_once
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once
函数。使用dispatch_once
函数能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once
也可以保证线程安全。
1 // 一次性代码(只执行一次) 2 gcd.once() 3 4 print("<-------------------------线程结束-------------------------->") 5 6 //一次性代码(只执行一次) 7 - (void)once { 8 static dispatch_once_t onceToken; 9 dispatch_once(&onceToken, ^{ 10 // 只执行1次的代码(这里面默认是线程安全的) 11 NSLog(@"只执行1次的代码(这里面默认是线程安全的)"); 12 }); 13 }
打印结果:
2018-03-29 23:44:53.632569+0800 StruggleSwift[1562:36219] 只执行1次的代码(这里面默认是线程安全的) <-------------------------线程结束--------------------------> <-------------------------线程结束--------------------------> <-------------------------线程结束-------------------------->
我把方法放在一个按钮的点击事件中,多次点击以后,通过上图可以发现,线程中的代码只会执行一次,所以它是安全的。
GCD 快速迭代方法:dispatch_apply
通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数dispatch_apply
。dispatch_apply
按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束,比如:apply--end 最后打印。
如果是在串行队列中使用 dispatch_apply
,那么就和 for 循环一样,按顺序同步执行。可这样就体现不出快速迭代的意义了。我们可以利用并发队列进行异步执行。比如说遍历 0~5 这6个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply
可以 在多个线程中同时(异步)遍历多个数字。还有一点,无论是在串行队列,还是异步队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait
方法。
1 // 快速迭代方法 dispatch_apply 2 gcd.apply() 3 print("<-------------------------线程结束-------------------------->") 4 5 6 //快速迭代方法 dispatch_apply 7 - (void)apply { 8 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 9 10 NSLog(@"apply---begin"); 11 dispatch_apply(6, queue, ^(size_t index) { 12 NSLog(@"%zd---%@",index, [NSThread currentThread]); 13 }); 14 NSLog(@"apply---end"); 15 }
打印结果:
1 2018-03-29 23:55:01.232112+0800 StruggleSwift[1798:45685] apply---begin 2 2018-03-29 23:55:07.599789+0800 StruggleSwift[1798:45685] 1---<NSThread: 0x604000073a80>{number = 1, name = main} 3 2018-03-29 23:55:07.599789+0800 StruggleSwift[1798:46278] 0---<NSThread: 0x608000275b80>{number = 9, name = (null)} 4 2018-03-29 23:55:07.599818+0800 StruggleSwift[1798:47017] 2---<NSThread: 0x604000460080>{number = 14, name = (null)} 5 2018-03-29 23:55:07.599820+0800 StruggleSwift[1798:47018] 3---<NSThread: 0x60c00007aa00>{number = 13, name = (null)} 6 2018-03-29 23:55:07.599859+0800 StruggleSwift[1798:47019] 4---<NSThread: 0x60c00007a2c0>{number = 15, name = (null)} 7 2018-03-29 23:55:07.599897+0800 StruggleSwift[1798:47020] 5---<NSThread: 0x6000002693c0>{number = 16, name = (null)} 8 2018-03-29 23:55:14.736663+0800 StruggleSwift[1798:45685] apply---end 9 <-------------------------线程结束-------------------------->
GCD 队列组:dispatch_group
有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
- 调用队列组的
dispatch_group_async
先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的dispatch_group_enter、dispatch_group_leave
组合 来实现
dispatch_group_async
。 - 调用队列组的
dispatch_group_notify
回到指定线程执行任务。或者使用dispatch_group_wait
回到当前线程继续向下执行(会阻塞当前线程)。
dispatch_group_notify
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
1 //队列组 dispatch_group_notify 2 - (void)groupNotify { 3 NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 4 NSLog(@"group---begin"); 5 6 dispatch_group_t group = dispatch_group_create(); 7 8 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 9 // 追加任务1 10 for (int i = 0; i < 2; ++i) { 11 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 12 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 13 } 14 }); 15 16 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 17 // 追加任务2 18 for (int i = 0; i < 2; ++i) { 19 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 20 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 21 } 22 }); 23 24 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 25 // 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务 26 for (int i = 0; i < 2; ++i) { 27 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 28 NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程 29 } 30 NSLog(@"group---end"); 31 }); 32 }
打印结果:
1 2018-03-30 00:10:53.721865+0800 StruggleSwift[2437:58949] currentThread---<NSThread: 0x60800007efc0>{number = 1, name = main} 2 2018-03-30 00:10:53.722043+0800 StruggleSwift[2437:58949] group---begin 3 <-------------------------线程结束--------------------------> 4 2018-03-30 00:10:55.727591+0800 StruggleSwift[2437:63976] 2---<NSThread: 0x60800047de00>{number = 20, name = (null)} 5 2018-03-30 00:10:55.727592+0800 StruggleSwift[2437:64429] 1---<NSThread: 0x60c000269ac0>{number = 19, name = (null)} 6 2018-03-30 00:10:57.733127+0800 StruggleSwift[2437:64429] 1---<NSThread: 0x60c000269ac0>{number = 19, name = (null)} 7 2018-03-30 00:10:57.733129+0800 StruggleSwift[2437:63976] 2---<NSThread: 0x60800047de00>{number = 20, name = (null)} 8 2018-03-30 00:10:59.734785+0800 StruggleSwift[2437:58949] 3---<NSThread: 0x60800007efc0>{number = 1, name = main} 9 2018-03-30 00:11:01.736370+0800 StruggleSwift[2437:58949] 3---<NSThread: 0x60800007efc0>{number = 1, name = main} 10 2018-03-30 00:11:01.736724+0800 StruggleSwift[2437:58949] group---end
结果看现象:从dispatch_group_notify
相关代码运行输出结果可以看出,当所有任务都执行完成之后,才执行dispatch_group_notify
block 中的任务。
dispatch_group_wait
1 //队列组 dispatch_group_wait 2 - (void)groupWait { 3 NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 4 NSLog(@"group---begin"); 5 6 dispatch_group_t group = dispatch_group_create(); 7 8 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 9 // 追加任务1 10 for (int i = 0; i < 2; ++i) { 11 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 12 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 13 } 14 }); 15 16 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 17 // 追加任务2 18 for (int i = 0; i < 2; ++i) { 19 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 20 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 21 } 22 }); 23 24 // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程) 25 dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 26 27 NSLog(@"group---end"); 28 }
打印结果:
1 2018-03-30 10:48:18.536481+0800 StruggleSwift[1090:22388] currentThread---<NSThread: 0x600000065080>{number = 1, name = main} 2 2018-03-30 10:48:18.536728+0800 StruggleSwift[1090:22388] group---begin 3 2018-03-30 10:48:30.950636+0800 StruggleSwift[1090:22903] 1---<NSThread: 0x608000078880>{number = 3, name = (null)} 4 2018-03-30 10:48:34.966640+0800 StruggleSwift[1090:22903] 1---<NSThread: 0x608000078880>{number = 3, name = (null)} 5 2018-03-30 10:48:34.966646+0800 StruggleSwift[1090:23381] 2---<NSThread: 0x60800006de00>{number = 4, name = (null)} 6 2018-03-30 10:48:37.922334+0800 StruggleSwift[1090:23381] 2---<NSThread: 0x60800006de00>{number = 4, name = (null)} 7 2018-03-30 10:48:40.322614+0800 StruggleSwift[1090:22388] group---end 8 <-------------------------线程结束-------------------------->
现象看本质:当所有任务执行完成之后,才执行 dispatch_group_wait
之后的操作。但是,使用dispatch_group_wait
会阻塞当前线程。
dispatch_group_enter、dispatch_group_leave
dispatch_group_enter
标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1dispatch_group_leave
标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1。- 当 group 中未执行完毕任务数为0的时候,才会使
dispatch_group_wait
解除阻塞,以及执行追加到dispatch_group_notify
中的任务。
1 //队列组 dispatch_group_enter、dispatch_group_leave 2 - (void)groupEnterAndLeave 3 { 4 NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 5 NSLog(@"group---begin"); 6 7 dispatch_group_t group = dispatch_group_create(); 8 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 9 dispatch_group_enter(group); 10 dispatch_async(queue, ^{ 11 // 追加任务1 12 for (int i = 0; i < 2; ++i) { 13 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 14 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 15 } 16 dispatch_group_leave(group); 17 }); 18 19 dispatch_group_enter(group); 20 dispatch_async(queue, ^{ 21 // 追加任务2 22 for (int i = 0; i < 2; ++i) { 23 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 24 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 25 } 26 dispatch_group_leave(group); 27 }); 28 29 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 30 // 等前面的异步操作都执行完毕后,回到主线程. 31 for (int i = 0; i < 2; ++i) { 32 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 33 NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程 34 } 35 NSLog(@"group---end"); 36 }); 37 38 // // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程) 39 // dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 40 // 41 // NSLog(@"group---end"); 42 }
1 2018-03-30 11:06:09.424085+0800 StruggleSwift[1514:37488] currentThread---<NSThread: 0x60000006fac0>{number = 1, name = main} 2 2018-03-30 11:06:09.424244+0800 StruggleSwift[1514:37488] group---begin 3 <-------------------------线程结束--------------------------> 4 2018-03-30 11:06:22.208661+0800 StruggleSwift[1514:43983] 1---<NSThread: 0x600000273fc0>{number = 13, name = (null)} 5 2018-03-30 11:06:28.076730+0800 StruggleSwift[1514:43983] 1---<NSThread: 0x600000273fc0>{number = 13, name = (null)} 6 2018-03-30 11:06:28.076747+0800 StruggleSwift[1514:44962] 2---<NSThread: 0x600000273240>{number = 14, name = (null)} 7 2018-03-30 11:06:32.059499+0800 StruggleSwift[1514:44962] 2---<NSThread: 0x600000273240>{number = 14, name = (null)} 8 2018-03-30 11:06:40.543141+0800 StruggleSwift[1514:37488] 3---<NSThread: 0x60000006fac0>{number = 1, name = main} 9 2018-03-30 11:06:43.619775+0800 StruggleSwift[1514:37488] 3---<NSThread: 0x60000006fac0>{number = 1, name = main} 10 2018-03-30 11:06:43.620013+0800 StruggleSwift[1514:37488] group---end
现象看本质:当所有任务执行完成之后,才执行 dispatch_group_notify 中的任务。这里的dispatch_group_enter、dispatch_group_leave
组合,其实等同于dispatch_group_async
。
dispatch_get_global_queue(long identifier, unsigned long flags) 并行队列
简介:
long identifier: 作为线程的优先级使用;
- DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT //当值为 0 时,表示默认
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
unsigned long flags: 未来变量来使用的;
线程优先级
1 - (void) priority { 2 dispatch_queue_t q1 = dispatch_queue_create("com.q1", DISPATCH_QUEUE_SERIAL); 3 dispatch_queue_t q2 = dispatch_queue_create("com.q2", DISPATCH_QUEUE_SERIAL); 4 dispatch_queue_t q3 = dispatch_queue_create("com.q3", DISPATCH_QUEUE_SERIAL); 5 6 //设置优先级 7 dispatch_set_target_queue(q1, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); 8 9 dispatch_async(q1, ^{ 10 NSLog(@"1"); //优先级降低了 11 }); 12 dispatch_async(q2, ^{ 13 NSLog(@"2"); 14 }); 15 dispatch_async(q3, ^{ 16 sleep(1); 17 NSLog(@"3"); 18 }); 19 }
打印结果:
1 2018-06-25 00:00:02.643135+0800 BlockTest[1304:102000] 2 2 2018-06-25 00:00:02.643161+0800 BlockTest[1304:101997] 1 3 2018-06-25 00:00:03.644093+0800 BlockTest[1304:101998] 3
栅栏
1 - (void) barrier { 2 // dispatch_get_global_queue(0, 0);不能使用全局和串行队列,否则它只能是异步的,没什么用了。必须是自定义并发的队列 3 dispatch_queue_t q = dispatch_queue_create("com.barrier", DISPATCH_QUEUE_CONCURRENT); 4 dispatch_async(q, ^{ 5 NSLog(@"1"); 6 }); 7 dispatch_async(q, ^{ 8 NSLog(@"2"); 9 }); 10 11 // 像一个栅栏一样,把上下两部分隔开,都是随机的 12 dispatch_barrier_async(q, ^{ 13 NSLog(@"barrier"); 14 }); 15 16 dispatch_async(q, ^{ 17 NSLog(@"3"); 18 }); 19 dispatch_async(q, ^{ 20 NSLog(@"4"); 21 }); 22 }
打印结果:
2018-06-25 08:24:31.630939+0800 BlockTest[1005:24509] 1 2018-06-25 08:24:31.630946+0800 BlockTest[1005:24515] 2 2018-06-25 08:24:31.631125+0800 BlockTest[1005:24515] barrier 2018-06-25 08:24:31.631448+0800 BlockTest[1005:24515] 3 2018-06-25 08:24:31.631452+0800 BlockTest[1005:24509] 4
线程安全
线程安全:内存的数据被多个线程读写,出现的结果是可遇见的
锁:保护线程安全
锁:确保只有一条线程在读取数据
锁的种类以及性能图:
OSSpinLock 自旋锁(虽然已经被证明不安全 优先级翻转),性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务。对于内存缓存的存取来说,它非常合适。
dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。对磁盘缓存来说,它比较合适。
线程不安全:
1 - (void) synchronized { 2 dispatch_async(dispatch_get_global_queue(0, 0), ^{ 3 NSLog(@"1"); 4 sleep(2); 5 NSLog(@"1 ok"); 6 }); 7 8 dispatch_async(dispatch_get_global_queue(0, 0), ^{ 9 NSLog(@"2"); 10 sleep(2); 11 NSLog(@"2 ok"); 12 }); 13 }
打印结果:
2018-06-25 08:35:42.019703+0800 BlockTest[1088:30404] 2 2018-06-25 08:35:42.019731+0800 BlockTest[1088:30405] 1 2018-06-25 08:35:44.023813+0800 BlockTest[1088:30404] 2 ok 2018-06-25 08:35:44.023812+0800 BlockTest[1088:30405] 1 ok
线程安全:
synchronized 锁(互斥锁[也叫同步锁]:其他线程):简单,效率最低
1 - (void) synchronized { 2 dispatch_async(dispatch_get_global_queue(0, 0), ^{ 3 @synchronized(self) { 4 NSLog(@"1"); 5 sleep(2); 6 NSLog(@"1 ok"); 7 } 8 }); 9 10 dispatch_async(dispatch_get_global_queue(0, 0), ^{ 11 @synchronized(self) { 12 NSLog(@"2"); 13 sleep(2); 14 NSLog(@"2 ok"); 15 } 16 }); 17 }
打印结果:
1 2018-06-25 08:46:40.233467+0800 BlockTest[1181:36806] 1 2 2018-06-25 08:46:42.239071+0800 BlockTest[1181:36806] 1 ok 3 2018-06-25 08:46:42.239650+0800 BlockTest[1181:36808] 2 4 2018-06-25 08:46:44.239926+0800 BlockTest[1181:36808] 2 ok
@synchronzied 若使用不当,可能会造成死锁。请看这里。
GCD 信号量(自旋锁:其他线程正在执行我们锁定的代码,其他线程就会进入死循环,处在等待中。):dispatch_semaphore
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数为0时等待,不可通过。计数为1或大于1时,计数减1且不等待,可通过。
Dispatch Semaphore 提供了三个函数。
-
dispatch_semaphore_create
:创建一个Semaphore并初始化信号的总量 -
dispatch_semaphore_signal
:发送一个信号,让信号总量加1 -
dispatch_semaphore_wait
:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
注意:注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。
Dispatch Semaphore 在实际开发中主要用于:
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
Demo
1 - (void) semaphore { 2 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); //这个1代表多少条并发数,写为1,确保1个线程在走 3 dispatch_async(dispatch_get_global_queue(0, 0), ^{ 4 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 加锁,本来信号量是 1,在这里是 -1 5 NSLog(@"1"); 6 sleep(2); 7 NSLog(@"1 ok"); 8 dispatch_semaphore_signal(semaphore); //解锁, 信号量 +1, 信号量 >=0 ,才可以执行其中的代码块 9 }); 10 11 dispatch_async(dispatch_get_global_queue(0, 0), ^{ 12 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 13 NSLog(@"2"); 14 sleep(2); 15 NSLog(@"2 ok"); 16 dispatch_semaphore_signal(semaphore); 17 }); 18 }
打印结果:
1 2018-06-25 09:00:51.991505+0800 BlockTest[1293:44721] 1 2 2018-06-25 09:00:53.992362+0800 BlockTest[1293:44721] 1 ok 3 2018-06-25 09:00:53.992792+0800 BlockTest[1293:44720] 2 4 2018-06-25 09:00:55.998304+0800 BlockTest[1293:44720] 2 ok
Dispatch Semaphore 线程同步
tasksForKeyPath:
方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。1 //semaphore 线程同步 2 - (void)semaphoreSync { 3 4 NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 5 NSLog(@"semaphore---begin"); 6 7 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 8 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 9 10 __block int number = 0; 11 dispatch_async(queue, ^{ 12 // 追加任务1 13 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 14 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 15 16 number = 100; 17 18 dispatch_semaphore_signal(semaphore); 19 }); 20 21 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 22 NSLog(@"semaphore---end,number = %zd",number); 23 }
1 2018-03-31 12:28:40.177072+0800 StruggleSwift[6521:334246] currentThread---<NSThread: 0x600000076100>{number = 1, name = main} 2 2018-03-31 12:28:40.829169+0800 StruggleSwift[6521:334246] semaphore---begin 3 2018-03-31 12:28:56.402609+0800 StruggleSwift[6521:335300] 1---<NSThread: 0x604000278300>{number = 3, name = (null)} 4 2018-03-31 12:29:03.504049+0800 StruggleSwift[6521:334246] semaphore---end,number = 100 5 <-------------------------线程结束-------------------------->
分析原因:
semaphore---end
是在执行完 number = 100;
之后才打印的。而且输出结果 number 为 100。异步执行
不会做任何等待,可以继续执行任务。异步执行
将任务1追加到队列之后,不做等待,接着执行dispatch_semaphore_wait
方法。此时 semaphore == 0,当前线程进入等待状态。然后,异步任务1开始执行。任务1执行到dispatch_semaphore_signal
之后,总信号量,此时 semaphore == 1,dispatch_semaphore_wait
方法使总信号量减1,正在被阻塞的线程(主线程)恢复继续执行。最后打印semaphore---end,number = 100
。这样就实现了线程同步,将异步执行任务转换为同步执行任务。Dispatch Semaphore 线程安全和线程同步(为线程加锁)
线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
举个简单例子就是:两个人在一起聊天。两个人不能同时说话,避免听不清(操作冲突)。等一个人说完(一个线程结束操作),另一个再说(另一个线程再开始操作)。
下面,我们模拟火车票售卖的方式,实现 NSThread 线程安全和解决线程同步问题。
场景:总共有50张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。
非线程安全(不使用 semaphore)
1 //非线程安全:不使用 semaphore 2 //初始化火车票数量、卖票窗口(非线程安全)、并开始卖票 3 - (void)initTicketStatusNotSave { 4 NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 5 NSLog(@"semaphore---begin"); 6 7 self.ticketSurplusCount = 10; 8 9 // queue1 代表北京火车票售卖窗口 10 dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL); 11 // queue2 代表上海火车票售卖窗口 12 dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL); 13 14 __weak typeof(self) weakSelf = self; 15 dispatch_async(queue1, ^{ 16 [weakSelf saleTicketNotSafe]; 17 }); 18 19 dispatch_async(queue2, ^{ 20 [weakSelf saleTicketNotSafe]; 21 }); 22 } 23 24 //售卖火车票(非线程安全) 25 - (void)saleTicketNotSafe { 26 while (1) { 27 28 if (self.ticketSurplusCount > 0) { //如果还有票,继续售卖 29 self.ticketSurplusCount--; 30 NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]); 31 [NSThread sleepForTimeInterval:0.2]; 32 } else { //如果已卖完,关闭售票窗口 33 NSLog(@"所有火车票均已售完"); 34 break; 35 } 36 37 } 38 }
打印结果:
1 <-----------------2018-03-31 12:54:10.615223+0800 StruggleSwift[7198:360710] 剩余票数:9 窗口:<NSThread: 0x60800026e2c0>{number = 5, name = (null)} 2 --------线程结束--------------------------> 3 2018-03-31 12:54:13.731504+0800 StruggleSwift[7198:361763] 剩余票数:8 窗口:<NSThread: 0x60800026d300>{number = 6, name = (null)} 4 2018-03-31 12:54:13.936378+0800 StruggleSwift[7198:361763] 剩余票数:6 窗口:<NSThread: 0x60800026d300>{number = 6, name = (null)} 5 2018-03-31 12:54:13.936379+0800 StruggleSwift[7198:360710] 剩余票数:7 窗口:<NSThread: 0x60800026e2c0>{number = 5, name = (null)} 6 2018-03-31 12:54:14.140956+0800 StruggleSwift[7198:361763] 剩余票数:4 窗口:<NSThread: 0x60800026d300>{number = 6, name = (null)} 7 2018-03-31 12:54:14.140956+0800 StruggleSwift[7198:360710] 剩余票数:5 窗口:<NSThread: 0x60800026e2c0>{number = 5, name = (null)} 8 2018-03-31 12:54:14.345573+0800 StruggleSwift[7198:361763] 剩余票数:2 窗口:<NSThread: 0x60800026d300>{number = 6, name = (null)} 9 2018-03-31 12:54:14.345573+0800 StruggleSwift[7198:360710] 剩余票数:3 窗口:<NSThread: 0x60800026e2c0>{number = 5, name = (null)} 10 2018-03-31 12:54:14.549357+0800 StruggleSwift[7198:360710] 剩余票数:0 窗口:<NSThread: 0x60800026e2c0>{number = 5, name = (null)} 11 2018-03-31 12:54:14.549367+0800 StruggleSwift[7198:361763] 剩余票数:1 窗口:<NSThread: 0x60800026d300>{number = 6, name = (null)} 12 2018-03-31 12:54:14.753734+0800 StruggleSwift[7198:360710] 所有火车票均已售完 13 2018-03-31 12:54:18.034888+0800 StruggleSwift[7198:361763] 所有火车票均已售完
结论:可以看到在不考虑线程安全,不使用 semaphore 的情况下,得到票数是错乱的,这样显然不符合我们的需求,所以我们需要考虑线程安全问题。
线程安全(使用 semaphore 加锁)
1 //线程安全:使用 semaphore 加锁 2 //初始化火车票数量、卖票窗口(线程安全)、并开始卖票 3 - (void)initTicketStatusSave { 4 NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 5 NSLog(@"semaphore---begin"); 6 7 semaphoreLock = dispatch_semaphore_create(1); 8 9 self.ticketSurplusCount = 10; 10 11 // queue1 代表北京火车票售卖窗口 12 dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL); 13 // queue2 代表上海火车票售卖窗口 14 dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL); 15 16 __weak typeof(self) weakSelf = self; 17 dispatch_async(queue1, ^{ 18 [weakSelf saleTicketSafe]; 19 }); 20 21 dispatch_async(queue2, ^{ 22 [weakSelf saleTicketSafe]; 23 }); 24 } 25 26 //售卖火车票(线程安全) 27 - (void)saleTicketSafe { 28 while (1) { 29 // 相当于加锁 30 dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER); 31 32 if (self.ticketSurplusCount > 0) { //如果还有票,继续售卖 33 self.ticketSurplusCount--; 34 NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]); 35 [NSThread sleepForTimeInterval:0.2]; 36 } else { //如果已卖完,关闭售票窗口 37 NSLog(@"所有火车票均已售完"); 38 39 // 相当于解锁 40 dispatch_semaphore_signal(semaphoreLock); 41 break; 42 } 43 44 // 相当于解锁 45 dispatch_semaphore_signal(semaphoreLock); 46 } 47 }
打印结果:
1 2018-03-31 13:02:06.952639+0800 StruggleSwift[7598:371177] currentThread---<NSThread: 0x604000061980>{number = 1, name = main} 2 2018-03-31 13:02:06.952836+0800 StruggleSwift[7598:371177] semaphore---begin 3 <-------------------------线程结束--------------------------> 4 2018-03-31 13:02:06.953153+0800 StruggleSwift[7598:371672] 剩余票数:9 窗口:<NSThread: 0x60c00027b540>{number = 3, name = (null)} 5 2018-03-31 13:02:07.158734+0800 StruggleSwift[7598:371248] 剩余票数:8 窗口:<NSThread: 0x600000076dc0>{number = 4, name = (null)} 6 2018-03-31 13:02:07.362105+0800 StruggleSwift[7598:371672] 剩余票数:7 窗口:<NSThread: 0x60c00027b540>{number = 3, name = (null)} 7 2018-03-31 13:02:07.567641+0800 StruggleSwift[7598:371248] 剩余票数:6 窗口:<NSThread: 0x600000076dc0>{number = 4, name = (null)} 8 2018-03-31 13:02:07.769686+0800 StruggleSwift[7598:371672] 剩余票数:5 窗口:<NSThread: 0x60c00027b540>{number = 3, name = (null)} 9 2018-03-31 13:02:07.975049+0800 StruggleSwift[7598:371248] 剩余票数:4 窗口:<NSThread: 0x600000076dc0>{number = 4, name = (null)} 10 2018-03-31 13:02:08.175885+0800 StruggleSwift[7598:371672] 剩余票数:3 窗口:<NSThread: 0x60c00027b540>{number = 3, name = (null)} 11 2018-03-31 13:02:08.377316+0800 StruggleSwift[7598:371248] 剩余票数:2 窗口:<NSThread: 0x600000076dc0>{number = 4, name = (null)} 12 2018-03-31 13:02:08.582909+0800 StruggleSwift[7598:371672] 剩余票数:1 窗口:<NSThread: 0x60c00027b540>{number = 3, name = (null)} 13 2018-03-31 13:02:08.786384+0800 StruggleSwift[7598:371248] 剩余票数:0 窗口:<NSThread: 0x600000076dc0>{number = 4, name = (null)} 14 2018-03-31 13:02:08.987007+0800 StruggleSwift[7598:371672] 所有火车票均已售完 15 2018-03-31 13:02:08.987249+0800 StruggleSwift[7598:371248] 所有火车票均已售完
考虑了线程安全的情况下,使用 dispatch_semaphore
机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。
线程不安全:
1 - (void) test2 { 2 _number = 0; 3 dispatch_apply(100000, dispatch_get_global_queue(0, 0), ^(size_t index) { 4 self.number ++; 5 }); 6 7 NSLog(@"number = %d", self.number); 8 }
打印结果:
2018-06-25 09:46:44.002556+0800 BlockTest[1704:68408] number = 66782
结论:
atomic: 原子性
原子- 不可再划分,已经最小的一个操作单位(内存的读写),set方法加锁
使用atomic不一定安全,当线程数量开到100000,它会消耗性能
使用场景:适用于简单set/get方法操作,也就是只有一个set/get方法。当多个set/get方法操作时,它就不适合了,例如:self.number ++
natomic: UIKit的类使用它,因为UIKit没有使用多线程。
线程安全:
NSLock
1 @property(atomic, assign) int number; 2 3 4 - (void) test2 { 5 _number = 0; 6 NSLock *lock = [[NSLock alloc] init]; 7 dispatch_apply(100000, dispatch_get_global_queue(0, 0), ^(size_t index) { 8 [lock lock]; 9 self.number ++; 10 [lock unlock]; 11 }); 12 13 NSLog(@"number = %d", self.number); 14 }
打印结果:
2018-06-25 09:49:48.568766+0800 BlockTest[1734:70803] number = 100000
参考资料: