原文:https://www.jianshu.com/p/2d57c72016c6
概念
Grand Central Dispatch(GCD)是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。
优点
- GCD 可用于多核的并行运算
- GCD 会自动利用更多的 CPU 内核(比如双核、四核)
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
任务和队列
任务
任务就是执行操作的意思,换句话说就是线程内执行的代码,在 GCD 中放在 block 里。
执行任务的方式有两种:同步执行(sync)和异步执行(async),两者的主要区别是:是否等待队列里的任务结束以及是否具备开启新线程的能力。
- 同步执行(sync):
同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。只能在当前线程中执行任务,不具备开启新线程的能力。 - 异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。可以在新的线程中执行任务,具备开启新线程的能力。
注意:异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关(下面会讲)。
队列
这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
GCD 中的任务有两种:串行队列和并发队列,两者都符合先入先出原则,区别在于:执行顺序不同和开启线程数不同。
- 串行队列(Serial Dispatch Queue):
只开启一个线程,每次只执行一个任务,执行完这个任务继续下一个任务。 - 并发队列(Concurrent Dispatch Queue)“
可以在多个线程中,让多个任务并发(同时)执行。注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效
两个重点
队列与线程的关系:
主队列(main_queue)就是在主线程中的默认队列。并发队列(global_queue)就是新开辟线程中的队列。
一个线程中可以有多个串行的队列,但是无法拥有多个并发的队列,多个并发的队列会放在多个线程中。例如可以自定义队列加到主线程中同步执行,中断主队列任务执行完自定义队列中任务后继续执行主队列任务。
异步与并发的区别:
异步,是指执行顺序上,区别于同步的由上至下,异步会延后执行异步内的任务。而并发是指时间上的,不同于串行的等待,并发会同时执行并发队列中的任务。
GCD的使用步骤
GCD的使用有两步,第一步创建一个队列(串行队列或者并行队列),第二步将任务加入到等待队列中。
创建队列
通常使用dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)
来创建队列。第一个参数表示队列的唯一标识符,通常用逆序全程域名;第二个参数用来识别是串行队列还是并发队列:DISPATCH_QUEUE_SERIAL
表示串行队列,DISPATCH_QUEUE_CONCURRENT
表示并发队列。
//创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.dispatch.cp", DISPATCH_QUEUE_SERIAL);
//创建并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.dispatch.cp", DISPATCH_QUEUE_CONCURRENT);
获取队列
对于串行队列,GCD 提供了一种默认的特殊的串行队列:主队列(Main Dispatch Queue)。
所有放在主队列中的任务,都会放到主线程中执行。可使用 dispatch_get_main_queue()
获得主队列。
对于并发队列,GCD 供了一种默认的特殊的并发队列:全局并发队列(Global Dispatch Queue)。
可以使用 dispatch_get_global_queue
来获取。需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT
。第二个参数暂时没用,用0即可。
//获取默认串行队列(主队列)
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//获取默认并发队列 (全局并发队列)
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
创建任务
GCD 提供了同步执行任务的创建方法 dispatch_sync
和异步执行任务创建方法 dispatch_async
。
//创建同步任务
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, ^{
//这里放任务的执行方法
})
//创建异步任务
dispatch_async(<#dispatch_queue_t _Nonnull queue#>, ^{
//这里放任务的执行方法
})
虽然使用 GCD 只需两步,但是既然我们有两种队列(串行队列/并发队列),两种任务执行方式(同步执行/异步执行),那么我们就有了四种不同的组合方式。实际上,刚才还说了两种特殊队列:全局并发队列、主队列。全局并发队列可以作为普通并发队列来使用。但是主队列因为有点特殊,所以我们就又多了两种组合方式。这样就有六种不同的组合方式了。
- 同步执行 + 并发队列
无效。同步无法开启新线程,不能实现并发。 - 异步执行 + 并发队列
多线程的常用模式,异步并发,多线程同时进行多个任务。 - 同步执行 + 串行队列
普通串行,没有用到多线程。 - 异步执行 + 串行队列
多线程的常用模式,异步串行,新建一个线程串行执行多个任务。 - 同步执行 + 主队列
无效。新建任务需等待主队列任务结束,主队列后续任务需等待新建任务,互相等待,代码卡死崩溃。 - 异步执行 + 主队列
类似异步串行,只是没有新开辟线程,在主线程实现异步。
GCD六种组合下的情况
1.同步执行 + 并发队列
代码:
- (void)syncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); //打印当前线程
NSLog(@"syncConcurrent---begin");
//创建并发队列
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
//同步任务 + 并发队列
dispatch_sync(queue, ^{
// 自定义任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"任务1---%@",[NSThread currentThread]); //打印当前线程
}
});
dispatch_sync(queue, ^{
// 自定义任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"任务2---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_sync(queue, ^
{
// 自定义任务3
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"任务3---%@",[NSThread currentThread]);// 打印当前线程
}
});
NSLog(@"syncConcurrent---end");
}
log:
2018-04-08 15:07:13.433670+0800 应用加载时间的优化[5701:894068] currentThread---{number = 1, name = main}
2018-04-08 15:07:13.434007+0800 应用加载时间的优化[5701:894068] syncConcurrent---begin
2018-04-08 15:07:15.436162+0800 应用加载时间的优化[5701:894068] 任务1---{number = 1, name = main}
2018-04-08 15:07:17.437191+0800 应用加载时间的优化[5701:894068] 任务1---{number = 1, name = main}
2018-04-08 15:07:19.438426+0800 应用加载时间的优化[5701:894068] 任务2---{number = 1, name = main}
2018-04-08 15:07:21.439689+0800 应用加载时间的优化[5701:894068] 任务2---{number = 1, name = main}
2018-04-08 15:07:23.441261+0800 应用加载时间的优化[5701:894068] 任务3---{number = 1, name = main}
2018-04-08 15:07:25.442858+0800 应用加载时间的优化[5701:894068] 任务3---{number = 1, name = main}
2018-04-08 15:07:25.443222+0800 应用加载时间的优化[5701:894068] syncConcurrent---end
可以看到线程数一直为1,当前线程一直为主线程
总结:并发队列本身不能创建线程,而同步任务同样没有创建线程。因此一直为主线程同步效果,没有实现并发。
2.异步执行 + 并发队列
代码:
- (void)asyncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 自定义任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 自定义任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 自定义任务3
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
}
});
NSLog(@"asyncConcurrent---end");
}
log:
2018-04-08 22:01:41.497508+0800 应用加载时间的优化[6217:973574] currentThread---{number = 1, name = main}
2018-04-08 22:01:41.497657+0800 应用加载时间的优化[6217:973574] asyncConcurrent---begin
2018-04-08 22:01:41.498239+0800 应用加载时间的优化[6217:973574] asyncConcurrent---end
2018-04-08 22:01:44.622924+0800 应用加载时间的优化[6217:973669] 3---{number = 4, name = (null)}
2018-04-08 22:01:44.622929+0800 应用加载时间的优化[6217:973666] 2---{number = 3, name = (null)}
2018-04-08 22:01:46.624147+0800 应用加载时间的优化[6217:973667] 1---{number = 5, name = (null)}
2018-04-08 22:01:46.624234+0800 应用加载时间的优化[6217:973666] 2---{number = 3, name = (null)}
2018-04-08 22:01:46.624266+0800 应用加载时间的优化[6217:973669] 3---{number = 4, name = (null)}
2018-04-08 22:01:48.626508+0800 应用加载时间的优化[6217:973667] 1---{number = 5, name = (null)}
总结:可以看到开辟了四个线程(不知道为什么不是三个),任务交替/同时进行。异步拥有开启新线程的能力,并发执行任务。第一时间在主线程中执行了syncConcurrent---begin
和syncConcurrent---end
,然后在开启的三个线程中同时执行自定义的打印任务,然后在延迟三秒钟后再同时打印第二次。
3.同步执行 + 串行队列
代码:
- (void)syncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务3
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
}
});
NSLog(@"syncSerial---end");
}
log:
2018-04-08 22:27:00.564340+0800 应用加载时间的优化[6278:994824] currentThread---{number = 1, name = main}
2018-04-08 22:27:00.564452+0800 应用加载时间的优化[6278:994824] syncSerial---begin
2018-04-08 22:27:02.565885+0800 应用加载时间的优化[6278:994824] 1---{number = 1, name = main}
2018-04-08 22:27:04.566469+0800 应用加载时间的优化[6278:994824] 1---{number = 1, name = main}
2018-04-08 22:27:06.568109+0800 应用加载时间的优化[6278:994824] 2---{number = 1, name = main}
2018-04-08 22:27:08.569092+0800 应用加载时间的优化[6278:994824] 2---{number = 1, name = main}
2018-04-08 22:27:10.569759+0800 应用加载时间的优化[6278:994824] 3---{number = 1, name = main}
2018-04-08 22:27:12.571093+0800 应用加载时间的优化[6278:994824] 3---{number = 1, name = main}
2018-04-08 22:27:12.571398+0800 应用加载时间的优化[6278:994824] syncSerial---end
总结:所有任务都在主线程中,没有开辟新的线程。任务由上至下,间隔两秒,依次执行。
4.异步执行 + 串行队列
代码:
- (void)asyncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务3
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
}
});
NSLog(@"asyncSerial---end");
}
log:
2018-04-08 22:33:07.135609+0800 应用加载时间的优化[6314:1001768] currentThread---{number = 1, name = main}
2018-04-08 22:33:07.136200+0800 应用加载时间的优化[6314:1001768] asyncSerial---begin
2018-04-08 22:33:07.136992+0800 应用加载时间的优化[6314:1001768] asyncSerial---end
2018-04-08 22:33:09.137247+0800 应用加载时间的优化[6314:1001860] 1---{number = 3, name = (null)}
2018-04-08 22:33:11.142105+0800 应用加载时间的优化[6314:1001860] 1---{number = 3, name = (null)}
2018-04-08 22:33:13.143027+0800 应用加载时间的优化[6314:1001860] 2---{number = 3, name = (null)}
2018-04-08 22:33:15.145256+0800 应用加载时间的优化[6314:1001860] 2---{number = 3, name = (null)}
2018-04-08 22:33:17.147141+0800 应用加载时间的优化[6314:1001860] 3---{number = 3, name = (null)}
2018-04-08 22:33:19.151007+0800 应用加载时间的优化[6314:1001860] 3---{number = 3, name = (null)}
总结:在主线程中第一时间syncConcurrent---begin
和syncConcurrent---end
,然后在开启的新线程中串行执行自定义任务,依次间隔两秒。
5.同步执行 + 主队列
代码:
- (void)syncMain
{
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(dispatch_get_main_queue(), ^{
// 追加任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务3
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
}
});
NSLog(@"syncMain---end");
}
运行时会直接崩溃。
崩溃原因:这是因为我们在主线程中执行 syncMain 方法,相当于把 syncMain 任务放到了主线程的主队列中。而同步执行会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把任务1追加到主队列中,任务1就在等待主线程处理完 syncMain 任务。而 syncMain 任务需要等待任务1执行完毕,才能接着执行。
那么,现在的情况就是syncMain任务和任务1都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了。
而之前的同步串行和同步并发都可以执行不会崩溃的原因在于,他们虽然都在主线程中,但是并不在主队列(main_queue)中,而是在自定义的队列中。
6.异步执行 + 主队列
代码:
- (void)asyncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 追加任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务3
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
}
});
NSLog(@"asyncMain---end");
}
log:
2018-04-08 23:14:51.902008+0800 应用加载时间的优化[6448:1043174] currentThread---{number = 1, name = main}
2018-04-08 23:14:51.902183+0800 应用加载时间的优化[6448:1043174] asyncMain---begin
2018-04-08 23:14:51.902511+0800 应用加载时间的优化[6448:1043174] asyncMain---end
2018-04-08 23:14:53.906967+0800 应用加载时间的优化[6448:1043174] 1---{number = 1, name = main}
2018-04-08 23:14:55.907579+0800 应用加载时间的优化[6448:1043174] 1---{number = 1, name = main}
2018-04-08 23:14:57.909015+0800 应用加载时间的优化[6448:1043174] 2---{number = 1, name = main}
2018-04-08 23:14:59.909606+0800 应用加载时间的优化[6448:1043174] 2---{number = 1, name = main}
2018-04-08 23:15:01.910556+0800 应用加载时间的优化[6448:1043174] 3---{number = 1, name = main}
2018-04-08 23:15:03.912148+0800 应用加载时间的优化[6448:1043174] 3---{number = 1, name = main}
总结:类似于异步执行串行队列,都是在第一时间执行完syncConcurrent---begin
和 syncConcurrent---end
后按顺序间隔两秒执行任务。不同之处在于没有开辟新的线程,而是在主线程中异步执行。
所以参照上面所说的异步与并发的关系,实际上异步使自定义的任务延后执行,整个线程仍是在主队列中串行执行自定义任务。而其效果,与异步串行队列效果一致,都是异步串行,根本区别在于前者将任务放在主线程的主队列中,而后者将任务在子线程的自定义队列中,也就是前者只有一个线程,后者多开辟了一个线程。
线程间通信
在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。
使用dispatch_async(dispatch_get_main_queue(), ^{ });
回到主队列。
代码:
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
});
});
}
log:
2018-04-08 23:37:32.260716+0800 应用加载时间的优化[6512:1064267] 1---{number = 3, name = (null)}
2018-04-08 23:37:34.263732+0800 应用加载时间的优化[6512:1064267] 1---{number = 3, name = (null)}
2018-04-08 23:37:36.265455+0800 应用加载时间的优化[6512:1064179] 2---{number = 1, name = main}
总结:可以看到在其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作。
GCD的其他常用方法
栅栏方法:dispatch_barrier_async
我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到 dispatch_barrier_async
方法在两个操作组间形成栅栏。
代码:
- (void)barrier {
NSLog(@"barrier---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务3
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务4
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]);// 打印当前线程
}
});
NSLog(@"barrier---end");
}
log:
2018-04-10 18:52:30.142820+0800 应用加载时间的优化[7378:1317650] barrier---begin
2018-04-10 18:52:30.143018+0800 应用加载时间的优化[7378:1317650] barrier---end
2018-04-10 18:52:32.147032+0800 应用加载时间的优化[7378:1317775] 1---{number = 4, name = (null)}
2018-04-10 18:52:32.147034+0800 应用加载时间的优化[7378:1317777] 2---{number = 3, name = (null)}
2018-04-10 18:52:34.148931+0800 应用加载时间的优化[7378:1317777] 2---{number = 3, name = (null)}
2018-04-10 18:52:34.148931+0800 应用加载时间的优化[7378:1317775] 1---{number = 4, name = (null)}
2018-04-10 18:52:36.149834+0800 应用加载时间的优化[7378:1317775] barrier---{number = 4, name = (null)}
2018-04-10 18:52:38.151610+0800 应用加载时间的优化[7378:1317775] barrier---{number = 4, name = (null)}
2018-04-10 18:52:40.153601+0800 应用加载时间的优化[7378:1317775] 3---{number = 4, name = (null)}
2018-04-10 18:52:40.153601+0800 应用加载时间的优化[7378:1317777] 4---{number = 3, name = (null)}
2018-04-10 18:52:42.155339+0800 应用加载时间的优化[7378:1317775] 3---{number = 4, name = (null)}
2018-04-10 18:52:42.155339+0800 应用加载时间的优化[7378:1317777] 4---{number = 3, name = (null)}
总结:由于是异步,所有任务都异步执行。加入了栅栏,所有异步任务先执行栅栏前,再执行栅栏,最后执行栅栏后。
延时方法:dispatch_after
在指定时间(例如3秒)之后执行某个任务。可以用 GCD 的 dispatch_after 函数来实现。
需要注意的是:dispatch_after 函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 函数是很有效的。
代码:
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"after---begin");
// 2.0秒后异步追加任务代码到主队列,并开始执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"after---%@",[NSThread currentThread]);// 打印当前线程
});
NSLog(@"after---end");
}
log:
2018-04-10 19:08:03.125108+0800 应用加载时间的优化[7439:1332291] currentThread---{number = 1, name = main}
2018-04-10 19:08:03.125318+0800 应用加载时间的优化[7439:1332291] after---begin
2018-04-10 19:08:03.125458+0800 应用加载时间的优化[7439:1332291] after---end
2018-04-10 19:08:05.125544+0800 应用加载时间的优化[7439:1332291] after---{number = 1, name = main}
总结:可以看到是异步执行,没有卡主线程。延迟两秒后执行 block 内任务。
只执行一次代码:dispatch_once
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 函数。使用 dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。
代码:
+ (instancetype)instance
{
static CPReachability *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
instance.netType = CPAPPNetTypeWIFI;
instance.wifiName = [instance GetWifiName];
});
return instance;
}
总结:多用于单例。dispatch_once_t 其实是 long 类型,取其地址作为唯一标识符保证 block 内部任务执行且仅被执行一次。
快速迭代方法:dispatch_apply
通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数 dispatch_apply 。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
我们可以利用异步队列同时遍历。比如说遍历 0~5 这6个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以同时遍历多个数字。
代码:
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
[NSThread sleepForTimeInterval:2];//模拟耗时操作
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
log:
2018-04-10 19:18:38.640868+0800 应用加载时间的优化[7506:1345125] apply---begin
2018-04-10 19:18:40.642368+0800 应用加载时间的优化[7506:1345125] 0---{number = 1, name = main}
2018-04-10 19:18:40.646225+0800 应用加载时间的优化[7506:1345197] 3---{number = 5, name = (null)}
2018-04-10 19:18:40.646225+0800 应用加载时间的优化[7506:1345215] 1---{number = 4, name = (null)}
2018-04-10 19:18:40.646272+0800 应用加载时间的优化[7506:1345198] 2---{number = 3, name = (null)}
2018-04-10 19:18:42.643982+0800 应用加载时间的优化[7506:1345125] 4---{number = 1, name = main}
2018-04-10 19:18:42.647137+0800 应用加载时间的优化[7506:1345197] 5---{number = 5, name = (null)}
2018-04-10 19:18:42.647443+0800 应用加载时间的优化[7506:1345125] apply---end
总结:首先我们能看到 dispatch_apply 是并发执行的,因为在 queue 中,所以不能保证执行顺序。但是结果是同步的,会等待所有任务结束后继续线程,最后执行 apply---end
。优点在于大规模循环时比起 for 效率更高,比起手动开线程能防止线程数过多导致线程爆炸。
应用场景:
如果我们从服务器获取一个数组的数据,那么我们可以使用该方法从而快速的批量字典转模型。
dispatch_group
dispatch_group 是 GCD 中的一组方法,他有一个组的概念,可以把相关的任务归并到一个组内来执行,通过监听组内所有任务的执行情况来做相应处理。
dispatch_group_create
用于创建任务组
dispatch_group_t dispatch_group_create(void);
dispatch_group_async
把异步任务提交到指定任务组和指定下拿出队列执行
void dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
- group ——对应的任务组,之后可以通过dispatch_group_wait或者dispatch_group_notify监听任务组内任务的执行情况
- queue ——block任务执行的线程队列,任务组内不同任务的队列可以不同
- block —— 执行任务的block
dispatch_group_notify
待任务组执行完毕时调用,不会阻塞当前线程
代码:
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 追加任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 追加任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
}
NSLog(@"group---end");
});
}
log:
2018-04-10 19:31:53.446408+0800 应用加载时间的优化[7570:1360370] currentThread---{number = 1, name = main}
2018-04-10 19:31:53.446553+0800 应用加载时间的优化[7570:1360370] group---begin
2018-04-10 19:31:55.450356+0800 应用加载时间的优化[7570:1360470] 1---{number = 3, name = (null)}
2018-04-10 19:31:55.450352+0800 应用加载时间的优化[7570:1360463] 2---{number = 4, name = (null)}
2018-04-10 19:31:57.450882+0800 应用加载时间的优化[7570:1360470] 1---{number = 3, name = (null)}
2018-04-10 19:31:57.450882+0800 应用加载时间的优化[7570:1360463] 2---{number = 4, name = (null)}
2018-04-10 19:31:59.452501+0800 应用加载时间的优化[7570:1360370] 3---{number = 1, name = main}
2018-04-10 19:32:01.454278+0800 应用加载时间的优化[7570:1360370] 3---{number = 1, name = main}
2018-04-10 19:32:01.454655+0800 应用加载时间的优化[7570:1360370] group---end
总结:异步并发执行组一内任务和组二内任务,使用 dispatch_group_notify
监测以上两任务完成后回主线程进入 block 内执行任务三。全程异步,不卡线程。
dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
代码:
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 追加任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 追加任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
}
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
log:
2018-04-10 19:49:33.996536+0800 应用加载时间的优化[7766:1383789] currentThread---{number = 1, name = main}
2018-04-10 19:49:33.996684+0800 应用加载时间的优化[7766:1383789] group---begin
2018-04-10 19:49:36.000831+0800 应用加载时间的优化[7766:1383883] 2---{number = 3, name = (null)}
2018-04-10 19:49:36.000831+0800 应用加载时间的优化[7766:1383886] 1---{number = 4, name = (null)}
2018-04-10 19:49:38.001697+0800 应用加载时间的优化[7766:1383883] 2---{number = 3, name = (null)}
2018-04-10 19:49:38.001987+0800 应用加载时间的优化[7766:1383886] 1---{number = 4, name = (null)}
2018-04-10 19:49:38.003862+0800 应用加载时间的优化[7766:1383789] group---end
总结:使用 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
卡线程来等待 group 内容全部执行完之后继续下面任务,实现同步。如果去掉 dispatch_group_wait
则不卡线程,第一时间执行 group---end
,然后执行 group 中内容。
dispatch_group_enter、dispatch_group_leave
void dispatch_group_enter(dispatch_group_t group);
用于添加对应任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞和dispatch_group_notify 的 block执行。
void dispatch_group_leave(dispatch_group_t group);
用于减少任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enter 和 dispatch_group_leave 要匹配,不然系统会认为 group 任务没有执行完毕。
代码:
- (void)groupEnterAndLeave{
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务1
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务2
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程.
for(int i =0; i <2; ++i) {
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
}NSLog(@"group---end");
});//
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//
NSLog(@"group---end");
}
log:
2018-04-10 20:05:44.440633+0800 应用加载时间的优化[7796:1394822] currentThread---{number = 1, name = main}
2018-04-10 20:05:44.440802+0800 应用加载时间的优化[7796:1394822] group---begin
2018-04-10 20:05:44.441216+0800 应用加载时间的优化[7796:1394822] group---end
2018-04-10 20:05:46.443538+0800 应用加载时间的优化[7796:1394930] 2---{number = 3, name = (null)}
2018-04-10 20:05:46.443549+0800 应用加载时间的优化[7796:1394928] 1---{number = 4, name = (null)}
2018-04-10 20:05:48.445104+0800 应用加载时间的优化[7796:1394930] 2---{number = 3, name = (null)}
2018-04-10 20:05:48.445101+0800 应用加载时间的优化[7796:1394928] 1---{number = 4, name = (null)}
2018-04-10 20:05:50.445690+0800 应用加载时间的优化[7796:1394822] 3---{number = 1, name = main}
2018-04-10 20:05:52.447481+0800 应用加载时间的优化[7796:1394822] 3---{number = 1, name = main}
2018-04-10 20:05:52.447792+0800 应用加载时间的优化[7796:1394822] group---end
总结:等同于 dispatch_group_async
。
信号量: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 在实际开发中主要用于:
1.保持线程同步,将异步执行任务转换为同步执行任务
2.保证线程安全,为线程加锁
Dispatch Semaphore 线程同步
我们在开发中,会遇到这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。换句话说,相当于,将将异步执行任务转换为同步执行任务。比如说:AFNetworking 中 AFURLSessionManager.m 里面的tasksForKeyPath:方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。
代码:
- (void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任务1
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
number = 100;
dispatch_semaphore_signal(semaphore);
});
NSLog(@"main task continue");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}
log:
2018-04-10 21:58:57.974515+0800 应用加载时间的优化[8026:1443631] currentThread---{number = 1, name = main}
2018-04-10 21:58:57.974641+0800 应用加载时间的优化[8026:1443631] semaphore---begin
2018-04-10 21:58:57.974790+0800 应用加载时间的优化[8026:1443631] main task continue
2018-04-10 21:58:59.978823+0800 应用加载时间的优化[8026:1443709] 1---{number = 3, name = (null)}
2018-04-10 21:58:59.979016+0800 应用加载时间的优化[8026:1443631] semaphore---end,number = 100
总结:使用 semaphore 实现线程同步。可以在添加 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
之前实现主线程异步并发操作,在之后进行线程同步,等待子线程任务结束后执行后续操作。
线程安全和线程同步(为线程加锁)
线程安全:
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步:
可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
举个简单例子就是:两个人在一起聊天。两个人不能同时说话,避免听不清(操作冲突)。等一个人说完(一个线程结束操作),另一个再说(另一个线程再开始操作)。
以售票为例:
非线程安全:(不使用 semaphore):
代码:
- (void)initTicketStatusNotSafe {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"semaphore---begin");
self.ticketSurplusCount = 50;
// queue1 代表北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf =self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
- (void)saleTicketNotSafe
{
while(1) {
if(self.ticketSurplusCount >0) {
//如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@",self.ticketSurplusCount,[NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
}else{
//如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
break;
}
}
}
Log:
2018-04-10 22:25:01.047999+0800 应用加载时间的优化[8218:1472786] currentThread---{number = 1, name = main}
2018-04-10 22:25:01.048150+0800 应用加载时间的优化[8218:1472786] semaphore---begin
2018-04-10 22:25:01.048378+0800 应用加载时间的优化[8218:1472878] 剩余票数:48 窗口:{number = 4, name = (null)}
2018-04-10 22:25:01.048387+0800 应用加载时间的优化[8218:1472892] 剩余票数:49 窗口:{number = 3, name = (null)}
2018-04-10 22:25:01.249347+0800 应用加载时间的优化[8218:1472878] 剩余票数:47 窗口:{number = 4, name = (null)}
2018-04-10 22:25:01.249354+0800 应用加载时间的优化[8218:1472892] 剩余票数:46 窗口:{number = 3, name = (null)}
2018-04-10 22:25:01.450904+0800 应用加载时间的优化[8218:1472878] 剩余票数:45 窗口:{number = 4, name = (null)}
2018-04-10 22:25:01.450904+0800 应用加载时间的优化[8218:1472892] 剩余票数:45 窗口:{number = 3, name = (null)}
2018-04-10 22:25:01.651358+0800 应用加载时间的优化[8218:1472892] 剩余票数:44 窗口:{number = 3, name = (null)}
2018-04-10 22:25:01.651358+0800 应用加载时间的优化[8218:1472878] 剩余票数:44 窗口:{number = 4, name = (null)}
.
.
.
2018-04-10 22:25:07.753135+0800 应用加载时间的优化
[8218:1472878] 剩余票数:2 窗口:{number = 4, name = (null)}
2018-04-10 22:25:07.753135+0800 应用加载时间的优化[8218:1472892] 剩余票数:2 窗口:{number = 3, name = (null)}
2018-04-10 22:25:07.958573+0800 应用加载时间的优化[8218:1472878] 剩余票数:1 窗口:{number = 4, name = (null)}
2018-04-10 22:25:07.958574+0800 应用加载时间的优化[8218:1472892] 剩余票数:0 窗口:{number = 3, name = (null)}
总结:剩余票数错乱,且重复,存在多个线程同时访问的情况,也就说存在同一张票卖两次的情况。
线程安全:(使用 semaphore 加锁):
使用 semaphoreLock = dispatch_semaphore_create(1);
创建锁
使用 dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
加锁
使用 dispatch_semaphore_signal(semaphoreLock);
解锁
用加锁和解锁包含住可能会被多线程同时执行修改的代码。
代码:
static dispatch_semaphore_t semaphoreLock;
- (void)initTicketStatusSafe {
NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
NSLog(@"semaphore---begin");
//value表示最大线程同时访问数,此处为1,更改会出问题
semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount =50;
// queue1 代表北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
- (void)saleTicketSafe {
while(1) {
// 相当于加锁
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
if(self.ticketSurplusCount >0) {
//如果还有票,继续售卖
self.ticketSurplusCount--;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@",self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
}else{
//如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
break;
}// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
}
}
log:
2018-04-10 22:41:43.466847+0800 应用加载时间的优化[8424:1495856] currentThread---{number = 1, name = main}
2018-04-10 22:41:43.466991+0800 应用加载时间的优化[8424:1495856] semaphore---begin
2018-04-10 22:41:43.467215+0800 应用加载时间的优化[8424:1495959] 剩余票数:49 窗口:{number = 3, name = (null)}
2018-04-10 22:41:43.671865+0800 应用加载时间的优化[8424:1495961] 剩余票数:48 窗口:{number = 4, name = (null)}
2018-04-10 22:41:43.874748+0800 应用加载时间的优化[8424:1495959] 剩余票数:47 窗口:{number = 3, name = (null)}
2018-04-10 22:41:44.078812+0800 应用加载时间的优化[8424:1495961] 剩余票数:46 窗口:{number = 4, name = (null)}
2018-04-10 22:41:44.280894+0800 应用加载时间的优化[8424:1495959] 剩余票数:45 窗口:{number = 3, name = (null)}
2018-04-10 22:41:44.483298+0800 应用加载时间的优化[8424:1495961] 剩余票数:44 窗口:{number = 4, name = (null)}
2018-04-10 22:41:44.686228+0800 应用加载时间的优化[8424:1495959] 剩余票数:43 窗口:{number = 3, name = (null)}
2018-04-10 22:41:44.890615+0800 应用加载时间的优化[8424:1495961] 剩余票数:42 窗口:{number = 4, name = (null)}
.
.
.
2018-04-10 22:41:53.450842+0800 应用加载时间的优化[8424:1495961] 剩余票数:0 窗口:{number = 4, name = (null)}
2018-04-10 22:41:53.656188+0800 应用加载时间的优化[8424:1495959] 所有火车票均已售完
2018-04-10 22:41:53.656569+0800 应用加载时间的优化[8424:1495961] 所有火车票均已售完
总结:可以看出,在考虑了线程安全的情况下,使用 dispatch_semaphore
机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。这也就是线程锁的重要性。