引言: 越是细节越能体现一个人的严谨,越是微小越是能看到事物的光芒
1. 队列
官方解释:
DispathQueue是FIFO队列,应用程序可以以块对象的形式向其提交任务。调度队列可以串行执行任务,也可以并发执行任务。提交给DispathQueue的工作在系统管理的线程池上执行,不用开发者操心在那个线程上。除了App主线程的调度队列mainQueue外,别的DispathQueue系统不保证它执行任务的时候一直在一个具体的线程中。您可以同步或异步地安排工作项。当您同步地计划一个工作项时,代码将等待该项完成执行。当您异步地计划工作项时,代码在工作项运行到其他地方时继续执行。
避免创建过多线程
如果使用DispathQueue去并发执行任务时,不要再执行的时候阻塞任务执行的线程。如果阻塞了,系统会再额外创建线程去执行别的任务,如果任务快太多,系统可能会耗尽应用程序的线程。应用程序消耗太多线程的另一种方式是创建太多私有并发调度队列。因为每个调度队列都消耗线程资源,所以创建额外的并发调度队列会加剧线程消耗问题。不要创建私有并发队列,而是将任务提交到一个全局global并发调度队列。对于串行任务,请将串行队列的目标设置为全局并发队列之一。这样,您可以保持队列的序列化行为,同时最小化创建线程的独立队列的数量。
dispatch_queue_t dySerial = dispatch_queue_create("自己创建串行队列", DISPATCH_QUEUE_SERIAL); //自己创建串行队列
dispatch_queue_t dyConcurrent = dispatch_queue_create("自己创建并行队列", DISPATCH_QUEUE_CONCURRENT); //自己创建并行队列
dispatch_queue_t osMainSerial = dispatch_get_main_queue(); //系统默认主队列,就是主线程
dispatch_queue_t osConcurrent = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //系统默认的全局并行队列
2. 执行
//异步执行
dispatch_async("传入队列", 任务block);
//同步执行
dispatch_sync("传入队列", 任务block);
3. 不同执行 + 不同队列效果
3.1 同步执行 + 并行队列 不会创建多线程 顺序执行
//当前线程打印标记
NSLog(@"打印当前线程---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"*********同步执行 + 并行队列 不会创建多线程 顺序执行*********");
dispatch_sync(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务1线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务2线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务3线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
2018-02-26 15:58:11.853843+0800 GCD 系列知识点[47527:3342566] 打印当前线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.854101+0800 GCD 系列知识点[47527:3342566] *********同步执行 + 并行队列 不会创建多线程 顺序执行*********
2018-02-26 15:58:11.854512+0800 GCD 系列知识点[47527:3342566] 任务1线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.855069+0800 GCD 系列知识点[47527:3342566] 任务1线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.855570+0800 GCD 系列知识点[47527:3342566] 任务2线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.856073+0800 GCD 系列知识点[47527:3342566] 任务2线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.856731+0800 GCD 系列知识点[47527:3342566] 任务3线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.857079+0800 GCD 系列知识点[47527:3342566] 任务3线程---<NSThread: 0x600000069600>{number = 1, name = main}
3.2 异步执行 + 并发队列 会创建多个线程
NSLog(@"*********异步执行 + 并发队列 会创建多个线程 不是顺序执行*********");
dispatch_async(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务1线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务2线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(dyConcurrent, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务3线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
2018-02-26 15:59:55.983225+0800 GCD 系列知识点[47573:3349914] 打印当前线程---<NSThread: 0x600000076140>{number = 1, name = main}
2018-02-26 15:59:55.983441+0800 GCD 系列知识点[47573:3349914] *********异步执行 + 并发队列 会创建多个线程 不是顺序执行*********
2018-02-26 15:59:55.983760+0800 GCD 系列知识点[47573:3350179] 任务2线程---<NSThread: 0x604000679b80>{number = 5, name = (null)}
2018-02-26 15:59:55.983795+0800 GCD 系列知识点[47573:3350180] 任务1线程---<NSThread: 0x6000002798c0>{number = 4, name = (null)}
2018-02-26 15:59:55.983821+0800 GCD 系列知识点[47573:3350190] 任务3线程---<NSThread: 0x60000027a240>{number = 6, name = (null)}
2018-02-26 15:59:55.984116+0800 GCD 系列知识点[47573:3350179] 任务2线程---<NSThread: 0x604000679b80>{number = 5, name = (null)}
2018-02-26 15:59:55.984126+0800 GCD 系列知识点[47573:3350180] 任务1线程---<NSThread: 0x6000002798c0>{number = 4, name = (null)}
2018-02-26 15:59:55.984190+0800 GCD 系列知识点[47573:3350190] 任务3线程---<NSThread: 0x60000027a240>{number = 6, name = (null)}
3.3 同步执行 + 串行队列 不会创建新线程
NSLog(@"*********同步执行 + 串行队列 不会创建新线程*********");
dispatch_sync(dySerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务1线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(dySerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务2线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
2018-02-26 16:02:43.429624+0800 GCD 系列知识点[47637:3363265] 打印当前线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.429817+0800 GCD 系列知识点[47637:3363265] *********同步执行 + 串行队列 不会创建新线程*********
2018-02-26 16:02:43.430040+0800 GCD 系列知识点[47637:3363265] 任务1线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.430238+0800 GCD 系列知识点[47637:3363265] 任务1线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.430346+0800 GCD 系列知识点[47637:3363265] 任务2线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.430466+0800 GCD 系列知识点[47637:3363265] 任务2线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
3.4 异步执行 + 串行队列 只创建了一个线程(区别于当前线程的新线程)
NSLog(@"*********异步执行 + 串行队列 只创建了1个线程*********");
dispatch_async(dySerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务1线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(dySerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务2线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
2018-02-26 16:04:52.596042+0800 GCD 系列知识点[47689:3374647] 打印当前线程---<NSThread: 0x60000006f000>{number = 1, name = main}
2018-02-26 16:04:52.596243+0800 GCD 系列知识点[47689:3374647] *********异步执行 + 串行队列 只创建了1个线程*********
2018-02-26 16:04:52.596596+0800 GCD 系列知识点[47689:3375109] 任务1线程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
2018-02-26 16:04:52.596912+0800 GCD 系列知识点[47689:3375109] 任务1线程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
2018-02-26 16:04:52.597323+0800 GCD 系列知识点[47689:3375109] 任务2线程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
2018-02-26 16:04:52.597453+0800 GCD 系列知识点[47689:3375109] 任务2线程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
3.5 同步/异步 + 主队列dispatch_get_main_queue()执行效果
3.5.1 特别注意: 同步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 会出现死锁
NSLog(@"*********特别注意 异步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 会出现死锁 *********");
NSLog(@"任务1");
dispatch_sync(osMainSerial, ^{
NSLog(@"任务2线程---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"任务3");
Xcode运行 项目直接崩溃
首先执行任务1,然后遇到dispatch_sync 同步线程,当前线程进入等待,等待同步线程中的任务2执行完再执行任务3,这个任务2是加入到mainQueue主队列中。
dispatch_sync(osMainSerial,^(void)()) 是同步一个任务到主队列中,而当前去同步的正是主队列。因为队列的执行是FIFO(先进先出),所以两个都在等待对方完成,就会造成死锁。
3.5.2 如果这个操作放在非主队列中执行就不会有问题
//但是如果dispatch_sync(osMainSerial,^void()) 这个任务的执行是放在 非主线程中(其他子线程) 执行的,就没问题
//NSThread detachNewThread 开辟一个新线程执行方法
[NSThread detachNewThreadSelector:@selector(testOtherThreadGetMainSyncSomething) toTarget:self withObject:nil];
//测试 同步执行 + 主队列dispatch_get_main_queue() 放在其他线程中
- (void)testOtherThreadGetMainSyncSomething{
dispatch_queue_t osMainSerial = dispatch_get_main_queue(); //系统默认主队列,就是主线程
NSLog(@"任务1");
dispatch_sync(osMainSerial, ^{
NSLog(@"任务2线程---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"任务3");
}
2018-02-26 17:58:01.616851+0800 GCD 系列知识点[50280:3588248] 打印当前线程---<NSThread: 0x60400006aac0>{number = 1, name = main}
2018-02-26 17:58:01.617322+0800 GCD 系列知识点[50280:3588554] 任务1
2018-02-26 17:58:01.623797+0800 GCD 系列知识点[50280:3588248] 任务2线程---<NSThread: 0x60400006aac0>{number = 1, name = main}
2018-02-26 17:58:01.624153+0800 GCD 系列知识点[50280:3588554] 任务3
3.6 异步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 异步执行的时候 不会创建新线程,只会使用当前线程
NSLog(@"*********特别注意 异步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 异步执行的时候 不会创建新线程*********");
dispatch_async(osMainSerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务1线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(osMainSerial, ^{
for (int i = 0; i < 2; i++){
NSLog(@"任务2线程---%@",[NSThread currentThread]); // 打印当前线程
}
});
2018-02-26 17:52:39.747531+0800 GCD 系列知识点[50190:3564074] 打印当前线程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.747691+0800 GCD 系列知识点[50190:3564074] *********特别注意 异步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 异步执行的时候 不会创建新线程*********
2018-02-26 17:52:39.773217+0800 GCD 系列知识点[50190:3564074] 任务1线程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.773976+0800 GCD 系列知识点[50190:3564074] 任务1线程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.775269+0800 GCD 系列知识点[50190:3564074] 任务2线程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.776235+0800 GCD 系列知识点[50190:3564074] 任务2线程---<NSThread: 0x60000006bf80>{number = 1, name = main}
4. 关于死锁
4.1 在上3.5.1上介绍 同步执行(dispatch_sync
) + 主队列 = 死锁。那么如果同步执行的 + 非主队列 是否就一定不会出现死锁了?看下面的代码:
//测试 同步 + 串行队列(非组队列) 死锁
[self testAsyncSerialLockSometing];
//测试死锁 同步执行 + 串行队列(非主队列)死锁的情况
/**
用异步执行一个串行队列的到一个新的线程,然后再同步执行这个串行队列,会发生死锁
*/
- (void)testAsyncSerialLockSometing{
//这里自己创建一个串行队列new dySerial
dispatch_queue_t dySerial = dispatch_queue_create("new dySerial", DISPATCH_QUEUE_SERIAL); //自己创建串行队列
//先异步执行这个串行队列,这样可以得到一个子线程。这个串行队列现在是挂载到
dispatch_async(dySerial, ^{
NSLog(@"任务1线程---%@",[NSThread currentThread]); // 打印当前线程
//这里再同步执行这个串行队列
dispatch_sync(dySerial, ^{
NSLog(@"任务2线程---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"任务3线程---%@",[NSThread currentThread]); // 打印当前线程
});
}
2018-02-27 09:57:49.336615+0800 GCD 系列知识点[55106:3863926] 打印当前线程---<NSThread: 0x60400006efc0>{number = 1, name = main}
2018-02-27 09:57:49.336972+0800 GCD 系列知识点[55106:3864092] 任务1线程---<NSThread: 0x60400027ca80>{number = 3, name = (null)}
(lldb)
Thread 3: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
会发现还是会出现死锁的情况,综上所述不难发现这种死锁产生的原因:在串行队列A(主队列或自己创建的串行队列)的线程中同步执行一个任务,而且这个任务还是放在串行队列A中。这样就会出现死锁。
4.2 还有一种死锁,就是当前的串行队列被阻塞,然后同步任务到这个队列就会出现
//测试 主线程被阻塞 死锁
[self testThreadBusyLockSomething];
- (void)testThreadBusyLockSomething{
dispatch_queue_t dyConcurrent = dispatch_queue_create("dyConcurrent", DISPATCH_QUEUE_CONCURRENT); //自己创建并行队列
dispatch_async(dyConcurrent, ^{
NSLog(@"任务1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务2");
//这里有个死循环
while(1){
}
}
2018-02-27 10:38:31.763153+0800 GCD 系列知识点[55527:3978731] 打印当前线程---<NSThread: 0x604000065500>{number = 1, name = main}
2018-02-27 10:38:31.763335+0800 GCD 系列知识点[55527:3978731] 任务2
2018-02-27 10:38:31.763358+0800 GCD 系列知识点[55527:3978980] 任务1
执行顺序:
main队列中死循环不结束,任务3不执行,任务3又是global全局队列同步过去的,global中同步的任务3不执行完、任务4是不会执行的。
了解死锁的发生时机后就很容易理解,所谓队列(串行并行)就是一个顺序执行的甬道。所谓同步异步就是决定执行这个队列的时候是否开辟新的线程去执行。
4. 关于GCD之间的通信,就是一个概念记住就好。不同的队列直接相互通信,只需要在执行任务的时候同步或异步执行任务的时候调用别的队列传值,最常见的就是
// 获取全局并发队列
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]); // 打印当前线程
});
});
5. GCD常用方法
5.1 组操作dispatch_group
5.1.1 dispatch_group_notify、dispatch_group_wait 基本用法
异步执行多个耗时任务,然后多个任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
//用于创建任务组
dispatch_group_t dispatch_group_create(void);
//异步任务提交到指定任务组和指定下拿出队列执行
dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
//多个异步任务完成后调用block(不会阻塞当前线程)
dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
//多个异步任务完成后dispatch_group_wait这句话后面的代码才会执行(会阻塞当前线程,任务全部执行完毕或超时才会接触阻塞)
//注意:因为dispatch_group_wait会阻塞当前线程,所以一般情况不要放入主线程
dispatch_group_wait(dispatch_group_t group,
dispatch_time_t timeout);
//使用的时候成对使用,在执行某个任务前调用enter,待执行任务数+1
//在某个任务完成后,调用leave,待执行任务数-1
//只有所有任务都完成(任务数为0)才会触发dispatch_group_notify 或 dispatch_group_wait
dispatch_group_enter(dispatch_group_t group);
dispatch_group_leave(dispatch_group_t group);
看Demo
-(void)testGroup{
dispatch_group_t group = dispatch_group_create(); //创建zhu
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing1");
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing2");
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing3");
sleep(3);
}) ;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"接着做某事");
}
2018-02-28 16:16:29.172002+0800 GCD 系列知识点[14525:1509846] doSomeThing1
2018-02-28 16:16:29.172004+0800 GCD 系列知识点[14525:1509848] doSomeThing2
2018-02-28 16:16:29.172014+0800 GCD 系列知识点[14525:1509849] doSomeThing3
2018-02-28 16:16:32.174314+0800 GCD 系列知识点[14525:1509719] 接着做某事
2018-02-28 16:16:32.179732+0800 GCD 系列知识点[14525:1509719] 刷新界面
运行发现 ,所有任务都执行完毕了,NSLog(@"刷新界面");
这句代码才执行,NSLog(@"接着做某事");
这句代码才执行。
注意
1,dispatch_group_wait
会阻塞当前线程,什么时候在它之前group_async执行的block任务结束了,或者wait自己设置的时间超时了,代码才会往下执行。如果dispatch_group_wait
之前没有group_async block任务,它就会马上执行
2,dispatch_group_wait
和 dispatch_group_notify
同时出现,只有dispatch_group_wait
执行后, dispatch_group_notify
才会执行,不管dispatch_group_wait
在 dispatch_group_notify
之前还是之后。
dispatch_group_wait
会阻塞当前线程,所以不要放在App主线程。
5.1.2 dispatch_group_enter、dispatch_group_leave 基本用法
在介绍这个用法之前,我们先看一个例子
-(void)testGroup{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"doSomeThing1");
});
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing2");
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing3");
}) ;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});
}
2018-02-28 17:26:07.752043+0800 GCD 系列知识点[15102:1596389] doSomeThing2
2018-02-28 17:26:07.752051+0800 GCD 系列知识点[15102:1596391] doSomeThing3
2018-02-28 17:26:07.761563+0800 GCD 系列知识点[15102:1596119] 刷新界面
2018-02-28 17:26:09.756480+0800 GCD 系列知识点[15102:1596390] doSomeThing1
会发现doSomeThing1
还没执行完就进入dispatch_group_notify
打印刷新页面,这是为什么了?
看代码会发现 doSomeThing1
是放在了一个异步线程中执行的。但是dispatch_group_notify
只会监听dispatch_group_async
中执行的同步任务。如果dispatch_group_async
执行的是个异步任务,那notify 监听结果可能不是你想要的。
那我们能不能实现dispatch_group_async
执行多异步任务,同时还能监听到任务全部完成了?做到这些就需要dispatch_group_enter
和 dispatch_group_leave
了,看下面例子:
-(void)testGroup{
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"doSomeThing1");
dispatch_group_leave(group);
});
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing2");
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"doSomeThing3");
}) ;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});
}
2018-02-28 17:34:10.449511+0800 GCD 系列知识点[15199:1621095] doSomeThing3
2018-02-28 17:34:10.449512+0800 GCD 系列知识点[15199:1621093] doSomeThing2
2018-02-28 17:34:12.449752+0800 GCD 系列知识点[15199:1621094] doSomeThing1
2018-02-28 17:34:12.450328+0800 GCD 系列知识点[15199:1620866] 刷新界面
dispatch_group_enter 用来标记这个任务的开始,dispatch_group_leave用来标记任务真正完成。这样成对标记可以实现标记dispatch_group_async
任务的真正执行完毕,dispatch_group_notify
也就能监听到了。
记住:
1. dispatch_group_enter 和 dispatch_group_leave 是成对出现的,不然dispatch_group 队列会出错。
2. dispatch_group_enter调用n次,就需要dispatch_group_leave调用n次,这样dispatch_group_notify才会执行
3. dispatch_group_enter/dispatch_group_leave 可以在mainQuene上用,不会像Semaphore卡住主线程
5.1.3 用dispatch_group 处理多网络请求都返回在刷新页面
根据5.1.2 我们可以在实际开发中运用dispatch_group 来处理多个网络请求都完成的通知。因为我们的网络请求都是异步的,正好可以用dispatch_group_enter 和 dispatch_group_leave
实现标记完成。
下面写一段伪代码展示:
dispatch_group_t group = dispatch_group_create();
//网络请求1
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[异步网络请求1:{
成功Block:dispatch_group_leave(group);
失败Block:dispatch_group_leave(group);
}];
})
//网络请求2
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[异步网络请求3:{
成功Block:dispatch_group_leave(group);
失败Block:dispatch_group_leave(group);
}];
})
//网络请求3
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[异步网络请求3:{
成功Block:dispatch_group_leave(group);
失败Block:dispatch_group_leave(group);
}];
})
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有网络请求完毕");
});
这样就可以处理多网络请求同时完成的情况了。
5.2 只执行一次dispatch_once
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 函数。使用
dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once也可以保证线程安全。
- (void)test {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
}
5.3 延时执行方法:dispatch_after
在指定时间(例如3秒)之后执行某个任务。可以用 GCD 的dispatch_after函数来实现。
dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的
- (void)after {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
});
}
5.4 栅栏方法dispatch_barrier_async
我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。
如下,如果想
-(void)testBarrier{
dispatch_queue_t dyConcurrent = dispatch_queue_create("dyConcurrent1", DISPATCH_QUEUE_CONCURRENT); //自己创建并行队列
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dyConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任务1");
}
});
dispatch_group_async(group, dyConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任务2");
}
});
dispatch_barrier_async(dyConcurrent, ^{
NSLog(@"追加追加追加");
});
dispatch_group_async(group, dyConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任务3");
}
});
dispatch_group_async(group, dyConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任务4");
}
});
}
2018-03-01 09:16:35.722991+0800 GCD 系列知识点[16166:1788483] 打印当前线程---<NSThread: 0x60400007f880>{number = 1, name = main}
2018-03-01 09:16:35.723276+0800 GCD 系列知识点[16166:1788561] 任务1
2018-03-01 09:16:35.723288+0800 GCD 系列知识点[16166:1788563] 任务2
2018-03-01 09:16:35.723442+0800 GCD 系列知识点[16166:1788561] 任务1
2018-03-01 09:16:35.723452+0800 GCD 系列知识点[16166:1788563] 任务2
2018-03-01 09:16:35.723532+0800 GCD 系列知识点[16166:1788561] 任务1
2018-03-01 09:16:35.723549+0800 GCD 系列知识点[16166:1788563] 任务2
2018-03-01 09:16:35.723785+0800 GCD 系列知识点[16166:1788563] 追加追加追加
2018-03-01 09:16:35.724089+0800 GCD 系列知识点[16166:1788560] 任务3
2018-03-01 09:16:35.724357+0800 GCD 系列知识点[16166:1788563] 任务4
2018-03-01 09:16:35.725130+0800 GCD 系列知识点[16166:1788560] 任务3
2018-03-01 09:16:35.725511+0800 GCD 系列知识点[16166:1788563] 任务4
2018-03-01 09:16:35.725511+0800 GCD 系列知识点[16166:1788560] 任务3
2018-03-01 09:16:35.725900+0800 GCD 系列知识点[16166:1788563] 任务4
会发现,任务1和任务2 全部执行完毕后 在执行追加任务,然后再执行任务3和任务4,如果我们下面代码注释掉再运行一下
/*
dispatch_barrier_async(dyConcurrent, ^{
NSLog(@"追加追加追加");
});
*/
2018-03-01 09:19:36.735062+0800 GCD 系列知识点[16214:1797692] 任务1
2018-03-01 09:19:36.735062+0800 GCD 系列知识点[16214:1797691] 任务2
2018-03-01 09:19:36.735089+0800 GCD 系列知识点[16214:1797705] 任务3
2018-03-01 09:19:36.735116+0800 GCD 系列知识点[16214:1797693] 任务4
2018-03-01 09:19:36.735310+0800 GCD 系列知识点[16214:1797691] 任务2
2018-03-01 09:19:36.735315+0800 GCD 系列知识点[16214:1797692] 任务1
2018-03-01 09:19:36.735436+0800 GCD 系列知识点[16214:1797705] 任务3
2018-03-01 09:19:36.735623+0800 GCD 系列知识点[16214:1797693] 任务4
2018-03-01 09:19:36.735653+0800 GCD 系列知识点[16214:1797691] 任务2
2018-03-01 09:19:36.735716+0800 GCD 系列知识点[16214:1797692] 任务1
2018-03-01 09:19:36.736728+0800 GCD 系列知识点[16214:1797705] 任务3
2018-03-01 09:19:36.738247+0800 GCD 系列知识点[16214:1797693] 任务4
会发现各个任务是混合异步执行的。
通过这个例子我们能明白dispatch_barrier_async主要是分割异步任务的作用。
5.5 dispatch_apply 快速遍历
//参数iterations遍历次数,queue执行遍历任务的队列,block每次遍历回调
dispatch_apply(size_t iterations, dispatch_queue_t queue,
DISPATCH_NOESCAPE void (^block)(size_t));
和一般我们用for循环遍历不同的是,如果dispatch_apply
调用的时候传入queue
的是并发队列,那么它的遍历就是异步执行的,会在多个线程中进行,而我们直接for循环都是在当前线程中进行的。如下
-(void)testApply{
NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8"];
// dispatch_queue_t queue = dispatch_queue_create("new dySerial", DISPATCH_QUEUE_SERIAL); //自己创建串行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(imageArray.count, queue, ^(size_t index) {
NSLog(@"%@---%@",imageArray[index], [NSThread currentThread]);
});
}
018-03-01 09:49:13.824457+0800 GCD 系列知识点[16580:1890506] image1---<NSThread: 0x600000064e80>{number = 1, name = main}
2018-03-01 09:49:13.824554+0800 GCD 系列知识点[16580:1890719] image3---<NSThread: 0x60400047be00>{number = 5, name = (null)}
2018-03-01 09:49:13.824555+0800 GCD 系列知识点[16580:1890718] image2---<NSThread: 0x600000265780>{number = 4, name = (null)}
2018-03-01 09:49:13.824601+0800 GCD 系列知识点[16580:1890721] image4---<NSThread: 0x6040004647c0>{number = 3, name = (null)}
2018-03-01 09:49:13.824706+0800 GCD 系列知识点[16580:1890506] image5---<NSThread: 0x600000064e80>{number = 1, name = main}
2018-03-01 09:49:13.824805+0800 GCD 系列知识点[16580:1890719] image6---<NSThread: 0x60400047be00>{number = 5, name = (null)}
2018-03-01 09:49:13.824987+0800 GCD 系列知识点[16580:1890721] image8---<NSThread: 0x6040004647c0>{number = 3, name = (null)}
2018-03-01 09:49:13.824991+0800 GCD 系列知识点[16580:1890718] image7---<NSThread: 0x600000265780>{number = 4, name = (null)}
会发现打印的线程号都是不同的。但是如果传入的是个串行队列(非主队列)它就和普通的for循环没什么不同了。如下
-(void)testApply{
NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8"];
dispatch_queue_t dySerial = dispatch_queue_create("new dySerial", DISPATCH_QUEUE_SERIAL); //自己创建串行队列
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(imageArray.count, dySerial, ^(size_t index) {
NSLog(@"%@---%@",imageArray[index], [NSThread currentThread]);
});
}
2018-03-01 09:42:20.361535+0800 GCD 系列知识点[16487:1861258] image1---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.361714+0800 GCD 系列知识点[16487:1861258] image2---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.361844+0800 GCD 系列知识点[16487:1861258] image3---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362145+0800 GCD 系列知识点[16487:1861258] image4---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362262+0800 GCD 系列知识点[16487:1861258] image5---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362374+0800 GCD 系列知识点[16487:1861258] image6---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362489+0800 GCD 系列知识点[16487:1861258] image7---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362604+0800 GCD 系列知识点[16487:1861258] image8---<NSThread: 0x60000006e140>{number = 1, name = main}
会发现和不同的for循环没什么不同,都是在主线程中进行的。
注意:如果执行
dispatch_apply
的线程是串行队列A的线程,同时执行dispatch_apply
时传入queue
的也是串行队列A,会发生死锁。比如直接在主队列中执行dispatch_apply
,同时传入queue
也是主队列,如:
//直接调用会出现死锁
[self testApply];
-(void)testApply{
NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8"];
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_apply(imageArray.count, queue, ^(size_t index) {
NSLog(@"%@---%@",imageArray[index], [NSThread currentThread]);
});
}
当然这样做也没什么意义,一般
dispatch_apply
都是为了高效的异步的遍历数组。所以一般dispatch_apply
传入的queue
都是一个并发队列,同时还把dispatch_apply
整体方法一个异步执行的并发队列中,如:
NSArray *array = @[@"1", @"2", @"3", @"4", @"5", @"6"];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu : %@", index, [array objectAtIndex:index]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"currentThread = %@", [NSThread currentThread]);
NSLog(@"done");
});
});
5.6 信号量 dispatch_semaphore
干啥用的了,总结一句话:使用dispatch_semaphore 可以更好的控制并发多线程的任务处理。
//创建一个信号量,给他设置个信号量值
dispatch_semaphore_create(long value);
//可以使总信号量减1,当信号总量为0时就会一直等待,阻塞当前线程(当timeout 时间过后 也可以继续执行),否则就可以正常执行。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//发送一个信号,使信号总量加1
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
看着也看不出来有啥用,下面举例一些常见用法。
5.6.1 控制并发线程数量
下面来看看实际应用的例子:
现在我有一组图片地址,我需要开辟一个并发队列来下载这些图片,等这些图片都下载完了,告诉我我要去搞事情。
- (void)testSemaphoreSomething{
NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8",@"image8",@"image10"];
dispatch_queue_t dyConcurrent = dispatch_queue_create("dyConcurrent1", DISPATCH_QUEUE_CONCURRENT); //自己创建并行队列
// dispatch_semaphore_t cacheSemphore = dispatch_semaphore_create(5);
for (NSString *oneImage in imageArray){
dispatch_async(dyConcurrent, ^{
// dispatch_semaphore_wait(cacheSemphore, DISPATCH_TIME_FOREVER);
NSLog(@"下载图片:%@ 线程:%@",oneImage,[NSThread currentThread]);
sleep(1);
NSLog(@"下载完成");
// dispatch_semaphore_signal(cacheSemphore);
});
}
}
我们直接运行会发现会一下开辟imageArray.count
个线程来处理任务。
2018-02-27 18:22:11.014569+0800 GCD 系列知识点[1237:286459] 打印当前线程---<NSThread: 0x604000070200>{number = 1, name = main}
2018-02-27 18:22:11.014969+0800 GCD 系列知识点[1237:286648] 下载图片:image5 线程:<NSThread: 0x604000272240>{number = 7, name = (null)}
2018-02-27 18:22:11.014974+0800 GCD 系列知识点[1237:286651] 下载图片:image2 线程:<NSThread: 0x604000272140>{number = 4, name = (null)}
2018-02-27 18:22:11.015007+0800 GCD 系列知识点[1237:286647] 下载图片:image4 线程:<NSThread: 0x604000272200>{number = 6, name = (null)}
2018-02-27 18:22:11.015007+0800 GCD 系列知识点[1237:286650] 下载图片:image3 线程:<NSThread: 0x600000279080>{number = 5, name = (null)}
2018-02-27 18:22:11.015035+0800 GCD 系列知识点[1237:286649] 下载图片:image1 线程:<NSThread: 0x604000272100>{number = 3, name = (null)}
2018-02-27 18:22:11.015389+0800 GCD 系列知识点[1237:286668] 下载图片:image6 线程:<NSThread: 0x600000279100>{number = 8, name = (null)}
2018-02-27 18:22:11.015492+0800 GCD 系列知识点[1237:286669] 下载图片:image7 线程:<NSThread: 0x600000279180>{number = 9, name = (null)}
2018-02-27 18:22:11.015589+0800 GCD 系列知识点[1237:286670] 下载图片:image8 线程:<NSThread: 0x600000279200>{number = 10, name = (null)}
2018-02-27 18:22:11.015684+0800 GCD 系列知识点[1237:286671] 下载图片:image9 线程:<NSThread: 0x600000279280>{number = 11, name = (null)}
2018-02-27 18:22:11.015764+0800 GCD 系列知识点[1237:286672] 下载图片:image10 线程:<NSThread: 0x600000279300>{number = 12, name = (null)}
2018-02-27 18:22:12.017230+0800 GCD 系列知识点[1237:286651] 下载完成
2018-02-27 18:22:12.017232+0800 GCD 系列知识点[1237:286647] 下载完成
2018-02-27 18:22:12.017249+0800 GCD 系列知识点[1237:286648] 下载完成
2018-02-27 18:22:12.017253+0800 GCD 系列知识点[1237:286650] 下载完成
2018-02-27 18:22:12.017292+0800 GCD 系列知识点[1237:286649] 下载完成
2018-02-27 18:22:12.018566+0800 GCD 系列知识点[1237:286668] 下载完成
2018-02-27 18:22:12.023732+0800 GCD 系列知识点[1237:286671] 下载完成
2018-02-27 18:22:12.023728+0800 GCD 系列知识点[1237:286670] 下载完成
2018-02-27 18:22:12.023732+0800 GCD 系列知识点[1237:286669] 下载完成
2018-02-27 18:22:12.023761+0800 GCD 系列知识点[1237:286672] 下载完成
假如这里imageArray.count
是100多,很显然,我们这样的暴力处理是不切合实际的。使用 Semaphore我们可以控制同时并发的线程数量。我们打开屏蔽代码,看输出结果:
2018-02-27 18:25:25.741325+0800 GCD 系列知识点[1265:295325] 打印当前线程---<NSThread: 0x60400006e800>{number = 1, name = main}
2018-02-27 18:25:25.741625+0800 GCD 系列知识点[1265:295509] 下载图片:image2 线程:<NSThread: 0x60000027b380>{number = 4, name = (null)}
2018-02-27 18:25:25.741630+0800 GCD 系列知识点[1265:295508] 下载图片:image1 线程:<NSThread: 0x60400026fac0>{number = 3, name = (null)}
2018-02-27 18:25:25.741637+0800 GCD 系列知识点[1265:295510] 下载图片:image3 线程:<NSThread: 0x60000027b280>{number = 5, name = (null)}
2018-02-27 18:25:25.741677+0800 GCD 系列知识点[1265:295511] 下载图片:image4 线程:<NSThread: 0x60000027b240>{number = 6, name = (null)}
2018-02-27 18:25:25.742019+0800 GCD 系列知识点[1265:295525] 下载图片:image6 线程:<NSThread: 0x60400026fd00>{number = 7, name = (null)}
2018-02-27 18:25:26.742404+0800 GCD 系列知识点[1265:295509] 下载完成
2018-02-27 18:25:26.742442+0800 GCD 系列知识点[1265:295508] 下载完成
2018-02-27 18:25:26.742871+0800 GCD 系列知识点[1265:295525] 下载完成
2018-02-27 18:25:26.742871+0800 GCD 系列知识点[1265:295511] 下载完成
2018-02-27 18:25:26.742907+0800 GCD 系列知识点[1265:295510] 下载完成
2018-02-27 18:25:26.742965+0800 GCD 系列知识点[1265:295524] 下载图片:image5 线程:<NSThread: 0x604000270080>{number = 8, name = (null)}
2018-02-27 18:25:26.743013+0800 GCD 系列知识点[1265:295526] 下载图片:image7 线程:<NSThread: 0x60000027c080>{number = 9, name = (null)}
2018-02-27 18:25:26.743472+0800 GCD 系列知识点[1265:295527] 下载图片:image8 线程:<NSThread: 0x60000027c700>{number = 10, name = (null)}
2018-02-27 18:25:26.743472+0800 GCD 系列知识点[1265:295528] 下载图片:image9 线程:<NSThread: 0x60000027c640>{number = 11, name = (null)}
2018-02-27 18:25:26.743546+0800 GCD 系列知识点[1265:295529] 下载图片:image10 线程:<NSThread: 0x60400026ac80>{number = 12, name = (null)}
2018-02-27 18:25:27.746854+0800 GCD 系列知识点[1265:295528] 下载完成
2018-02-27 18:25:27.746912+0800 GCD 系列知识点[1265:295527] 下载完成
2018-02-27 18:25:27.746855+0800 GCD 系列知识点[1265:295524] 下载完成
2018-02-27 18:25:27.746954+0800 GCD 系列知识点[1265:295526] 下载完成
2018-02-27 18:25:27.746981+0800 GCD 系列知识点[1265:295529] 下载完成
会发现我们先开辟 5 个线程去下载,5个都下载完成了后再开辟5个去搞,这样就可以减轻系统并发的数量。
看结果你是不是感觉这有点像dispatch_barrier_async
都是把任务拆成几部分一块一块执行,但是他们是有本质不同的:dispatch_barrier_async
控制的是任务,它操作的对象是任务分块,不会处理线程的多少,而Semaphore控制同时并发的线程数量,再由一定的线程数量去执行任务。这好比同样是盖楼要10个人,barrier
类似于我把楼分成2部分,我们10个人先盖第一部分,第一部分完了再盖第二部分。而Semaphore
是我们同时只让5个人盖楼,剩下的休息,谁干完了谁休息,那个之前在休息的上来干。
那如果dispatch_semaphore_create(5)
改成dispatch_semaphore_create(1)
会怎么样,改完之后运行。
2018-02-27 18:29:33.880314+0800 GCD 系列知识点[1322:310027] 打印当前线程---<NSThread: 0x604000077200>{number = 1, name = main}
2018-02-27 18:29:33.880623+0800 GCD 系列知识点[1322:310152] 下载图片:image1 线程:<NSThread: 0x604000478c40>{number = 3, name = (null)}
2018-02-27 18:29:34.881307+0800 GCD 系列知识点[1322:310152] 下载完成
2018-02-27 18:29:34.881755+0800 GCD 系列知识点[1322:310155] 下载图片:image3 线程:<NSThread: 0x60400046f280>{number = 4, name = (null)}
2018-02-27 18:29:35.887236+0800 GCD 系列知识点[1322:310155] 下载完成
2018-02-27 18:29:35.887694+0800 GCD 系列知识点[1322:310153] 下载图片:image2 线程:<NSThread: 0x604000479540>{number = 5, name = (null)}
2018-02-27 18:29:36.891046+0800 GCD 系列知识点[1322:310153] 下载完成
2018-02-27 18:29:36.891503+0800 GCD 系列知识点[1322:310154] 下载图片:image4 线程:<NSThread: 0x60000007a380>{number = 6, name = (null)}
2018-02-27 18:29:37.894212+0800 GCD 系列知识点[1322:310154] 下载完成
2018-02-27 18:29:37.894732+0800 GCD 系列知识点[1322:310164] 下载图片:image5 线程:<NSThread: 0x600000263280>{number = 7, name = (null)}
2018-02-27 18:29:38.900181+0800 GCD 系列知识点[1322:310164] 下载完成
2018-02-27 18:29:38.900712+0800 GCD 系列知识点[1322:310166] 下载图片:image7 线程:<NSThread: 0x604000479740>{number = 8, name = (null)}
2018-02-27 18:29:39.904618+0800 GCD 系列知识点[1322:310166] 下载完成
2018-02-27 18:29:39.905013+0800 GCD 系列知识点[1322:310165] 下载图片:image6 线程:<NSThread: 0x600000263580>{number = 9, name = (null)}
2018-02-27 18:29:40.908401+0800 GCD 系列知识点[1322:310165] 下载完成
2018-02-27 18:29:40.908814+0800 GCD 系列知识点[1322:310168] 下载图片:image9 线程:<NSThread: 0x6000002631c0>{number = 10, name = (null)}
2018-02-27 18:29:41.910161+0800 GCD 系列知识点[1322:310168] 下载完成
2018-02-27 18:29:41.910634+0800 GCD 系列知识点[1322:310167] 下载图片:image8 线程:<NSThread: 0x6040004794c0>{number = 11, name = (null)}
2018-02-27 18:29:42.915150+0800 GCD 系列知识点[1322:310167] 下载完成
2018-02-27 18:29:42.915664+0800 GCD 系列知识点[1322:310169] 下载图片:image10 线程:<NSThread: 0x604000471980>{number = 12, name = (null)}
2018-02-27 18:29:43.919164+0800 GCD 系列知识点[1322:310169] 下载完成
会发现调用的是并发队列,但是限制信号量总数为1 后,并发队列就变成了串行队列。
5.6.2 对多线程并发执行任务共享资源进行加锁处理
案例:总共有20张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。
这里的问题核心是,两个窗口同时买票,在卖出一张票后总票数都会减1,如何确保票不多卖或卖叉?这就相当于计算机中的多条线程同时写一个资源,可能会出现混乱的问题。看Demo
-(void)testSaleTicketSemaphoreLock{
self.allTicket = 20;
//开辟两个线程来买票
NSThread *oneThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
oneThread.name = @"北京";
NSThread *twoThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
twoThread.name = @"上海";
[oneThread start];
[twoThread start];
}
-(void)saleTicket{
while (1) {
if (self.allTicket > 0){
self.allTicket --;
NSLog(@"剩余票数:%ld 当前窗口线程:%@",(long)self.allTicket,[NSThread currentThread].name);
[NSThread sleepForTimeInterval:1];
}else{
NSLog(@"所有票都买完了");
break;
}
}
}
2018-02-28 11:05:45.911240+0800 GCD 系列知识点[11745:1027862] 打印当前线程---<NSThread: 0x600000068140>{number = 1, name = main}
2018-02-28 11:05:45.911688+0800 GCD 系列知识点[11745:1028003] 剩余票数:19 当前窗口线程:北京
2018-02-28 11:05:45.911697+0800 GCD 系列知识点[11745:1028004] 剩余票数:18 当前窗口线程:上海
2018-02-28 11:05:46.916487+0800 GCD 系列知识点[11745:1028004] 剩余票数:17 当前窗口线程:上海
2018-02-28 11:05:46.916492+0800 GCD 系列知识点[11745:1028003] 剩余票数:16 当前窗口线程:北京
2018-02-28 11:05:47.922052+0800 GCD 系列知识点[11745:1028003] 剩余票数:15 当前窗口线程:北京
2018-02-28 11:05:47.922066+0800 GCD 系列知识点[11745:1028004] 剩余票数:14 当前窗口线程:上海
2018-02-28 11:05:48.925762+0800 GCD 系列知识点[11745:1028003] 剩余票数:13 当前窗口线程:北京
2018-02-28 11:05:48.925762+0800 GCD 系列知识点[11745:1028004] 剩余票数:12 当前窗口线程:上海
2018-02-28 11:05:49.930191+0800 GCD 系列知识点[11745:1028004] 剩余票数:11 当前窗口线程:上海
2018-02-28 11:05:49.930229+0800 GCD 系列知识点[11745:1028003] 剩余票数:11 当前窗口线程:北京
2018-02-28 11:05:50.934367+0800 GCD 系列知识点[11745:1028003] 剩余票数:10 当前窗口线程:北京
2018-02-28 11:05:50.934473+0800 GCD 系列知识点[11745:1028004] 剩余票数:9 当前窗口线程:上海
2018-02-28 11:05:51.937956+0800 GCD 系列知识点[11745:1028003] 剩余票数:8 当前窗口线程:北京
2018-02-28 11:05:51.937956+0800 GCD 系列知识点[11745:1028004] 剩余票数:8 当前窗口线程:上海
2018-02-28 11:05:52.942655+0800 GCD 系列知识点[11745:1028004] 剩余票数:7 当前窗口线程:上海
2018-02-28 11:05:52.942655+0800 GCD 系列知识点[11745:1028003] 剩余票数:7 当前窗口线程:北京
2018-02-28 11:05:53.945901+0800 GCD 系列知识点[11745:1028004] 剩余票数:6 当前窗口线程:上海
2018-02-28 11:05:53.945901+0800 GCD 系列知识点[11745:1028003] 剩余票数:6 当前窗口线程:北京
2018-02-28 11:05:54.949424+0800 GCD 系列知识点[11745:1028003] 剩余票数:5 当前窗口线程:北京
2018-02-28 11:05:54.949435+0800 GCD 系列知识点[11745:1028004] 剩余票数:4 当前窗口线程:上海
2018-02-28 11:05:55.952903+0800 GCD 系列知识点[11745:1028003] 剩余票数:3 当前窗口线程:北京
2018-02-28 11:05:55.952904+0800 GCD 系列知识点[11745:1028004] 剩余票数:2 当前窗口线程:上海
2018-02-28 11:05:56.956317+0800 GCD 系列知识点[11745:1028004] 剩余票数:1 当前窗口线程:上海
2018-02-28 11:05:56.956321+0800 GCD 系列知识点[11745:1028003] 剩余票数:0 当前窗口线程:北京
2018-02-28 11:05:57.961572+0800 GCD 系列知识点[11745:1028003] 所有票都买完了
2018-02-28 11:05:57.961599+0800 GCD 系列知识点[11745:1028004] 所有票都买完了
观察会发现余票在 11、8 、7、6的时候揣想那混乱,多卖了票。这就是多线程访问公共资源出现不同步的问题。我们使用dispatch_semaphore 来加锁处理一下,看看结果怎么样?
-(void)testSaleTicketSemaphoreLock{
self.allTicket = 20;
self.ticketSemaphore = dispatch_semaphore_create(1);
//开辟两个线程来买票
NSThread *oneThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
oneThread.name = @"北京";
NSThread *twoThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
twoThread.name = @"上海";
[oneThread start];
[twoThread start];
}
-(void)saleTicket{
while (1) {
//加锁 如果信号量>0 执行wait 信号量减1 下方代码执行,如果信号量<0 那就不执行下方代码 阻塞当前
dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);
if (self.allTicket > 0){
self.allTicket --;
NSLog(@"剩余票数:%ld 当前窗口线程:%@",(long)self.allTicket,[NSThread currentThread].name);
[NSThread sleepForTimeInterval:1];
}else{
NSLog(@"所有票都买完了");
dispatch_semaphore_signal(self.ticketSemaphore);
break;
}
//解锁 执行signal 信号量+1
dispatch_semaphore_signal(self.ticketSemaphore);
}
}
2018-02-28 14:17:28.118236+0800 GCD 系列知识点[13342:1195687] 打印当前线程---<NSThread: 0x604000068fc0>{number = 1, name = main}
2018-02-28 14:17:28.120479+0800 GCD 系列知识点[13342:1195906] 剩余票数:19 当前窗口线程:北京
2018-02-28 14:17:29.124787+0800 GCD 系列知识点[13342:1195907] 剩余票数:18 当前窗口线程:上海
2018-02-28 14:17:30.127421+0800 GCD 系列知识点[13342:1195906] 剩余票数:17 当前窗口线程:北京
2018-02-28 14:17:31.130508+0800 GCD 系列知识点[13342:1195907] 剩余票数:16 当前窗口线程:上海
2018-02-28 14:17:32.133827+0800 GCD 系列知识点[13342:1195906] 剩余票数:15 当前窗口线程:北京
2018-02-28 14:17:33.138644+0800 GCD 系列知识点[13342:1195907] 剩余票数:14 当前窗口线程:上海
2018-02-28 14:17:34.142570+0800 GCD 系列知识点[13342:1195906] 剩余票数:13 当前窗口线程:北京
2018-02-28 14:17:35.143104+0800 GCD 系列知识点[13342:1195907] 剩余票数:12 当前窗口线程:上海
2018-02-28 14:17:36.144129+0800 GCD 系列知识点[13342:1195906] 剩余票数:11 当前窗口线程:北京
2018-02-28 14:17:37.149313+0800 GCD 系列知识点[13342:1195907] 剩余票数:10 当前窗口线程:上海
2018-02-28 14:17:38.154850+0800 GCD 系列知识点[13342:1195906] 剩余票数:9 当前窗口线程:北京
2018-02-28 14:17:39.160380+0800 GCD 系列知识点[13342:1195907] 剩余票数:8 当前窗口线程:上海
2018-02-28 14:17:40.165849+0800 GCD 系列知识点[13342:1195906] 剩余票数:7 当前窗口线程:北京
2018-02-28 14:17:41.167534+0800 GCD 系列知识点[13342:1195907] 剩余票数:6 当前窗口线程:上海
2018-02-28 14:17:42.173125+0800 GCD 系列知识点[13342:1195906] 剩余票数:5 当前窗口线程:北京
2018-02-28 14:17:43.177330+0800 GCD 系列知识点[13342:1195907] 剩余票数:4 当前窗口线程:上海
2018-02-28 14:17:44.181994+0800 GCD 系列知识点[13342:1195906] 剩余票数:3 当前窗口线程:北京
2018-02-28 14:17:45.183557+0800 GCD 系列知识点[13342:1195907] 剩余票数:2 当前窗口线程:上海
2018-02-28 14:17:46.189193+0800 GCD 系列知识点[13342:1195906] 剩余票数:1 当前窗口线程:北京
2018-02-28 14:17:47.194826+0800 GCD 系列知识点[13342:1195907] 剩余票数:0 当前窗口线程:上海
2018-02-28 14:17:48.195913+0800 GCD 系列知识点[13342:1195906] 所有票都买完了
2018-02-28 14:17:48.196297+0800 GCD 系列知识点[13342:1195907] 所有票都买完了
会发现总剩余票数没有发生错位。其实dispatch_semaphore这里就是控制了多线程处理任务 并发时机,也是5.1.1的一种特殊形式。
5.6.3 页面有多个网络请求都完成后才处理任务
上面说的使用dispatch_group_enter
和dispatch_group_leave
可以实现多网络请求完成监听处理。我们使用dispatch_semaphore也可以完成这种操作,具体直接上下面那段伪代码
dispatch_group_t group = dispatch_group_create();
//网络请求1
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//创建一个为0信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[异步网络请求1:{
//成功或失败给信号加1
成功Block:dispatch_semaphore_signal(semaphore);
失败Block:dispatch_semaphore_signal(semaphore);
}];
//只有信号量>1 下面代码才走,方法才算执行完毕
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
//网络请求2
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//创建一个为0信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[异步网络请求2:{
//成功或失败给信号加1
成功Block:dispatch_semaphore_signal(semaphore);
失败Block:dispatch_semaphore_signal(semaphore);
}];
//只有信号量>1 下面代码才走,方法才算执行完毕
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
//网络请求3
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//创建一个为0信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[异步网络请求3:{
//成功或失败给信号加1
成功Block:dispatch_semaphore_signal(semaphore);
失败Block:dispatch_semaphore_signal(semaphore);
}];
//只有信号量>1 下面代码才走,方法才算执行完毕
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有网络请求完毕");
})
这样的组合就能实现多网络请求都有响应后的网络回调了!
补充: 那么如何让这些网络请求有个先后顺序了,就是说请求1返回回来的参数我要用到请求2上,这样的需求也很常见,用到的知识就不是GCD的了,而是NSOperation上的知识了,从别人博客弄来的代码,直接放上:
//按照顺序
NSBlockOperation *operation_1 = [NSBlockOperation blockOperationWithBlock:^{
[self request1];
}];
NSBlockOperation *operation_2 = [NSBlockOperation blockOperationWithBlock:^{
[self request2];
}];
NSBlockOperation *operation_3 = [NSBlockOperation blockOperationWithBlock:^{
[self request3];
}];
//设置依赖
[operation_2 addDependency:operation_1];
[operation_3 addDependency:operation_1];
//创建队列并添加任务
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperations:@[operation_3,operation_2,operation_1] waitUntilFinished:YES];
其中的[self request]
里面代码还是使用信号量来控制网络请求真正结束,拿request1代码举个栗子:
-(void)request1{
//创建一个为0信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[异步网络请求3:{
//成功或失败给信号加1
成功Block:dispatch_semaphore_signal(semaphore);
失败Block:dispatch_semaphore_signal(semaphore);
}];
//只有信号量>1 下面代码才走,方法才算执行完毕
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
以上都是我根据别人的博客和自己代码实践所得,如有冒犯和不足欢迎大家批评。
感觉在微小简单的东西,如果系统学习起来,还是有盲点的,所以说所有的事情态度要端正,虚心点吧
引用博客
https://www.jianshu.com/p/2d57c72016c6
http://blog.csdn.net/Cloudox_/article/details/71107179
https://www.jianshu.com/p/228403206664
https://www.cnblogs.com/zhou--fei/p/6747938.html
https://www.jianshu.com/p/aa3cfcabb470