一: 什么是GCD
以下摘自苹果的官方文档:
Grand Central Dispatch (GCD )是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加的适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样就比以前的线程更有效率。
GCD的代码示例:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
/**
//耗时操作。
*/
dispatch_async(dispatch_get_main_queue(), ^{
//执行只在主线程才执行的处理。比如更新UI
});
});
在GCD中,开发者只需要定义想执行的任务并追加到适当的dispatch Queue中。
dispatch_async(queue,^{
/*
想执行的任务。
*/
});
以上代码使用dispacth_asyn函数追加任务到赋值在变量queue的dispatch queue中,仅这样就可以使block 在另一线程中执行。
dispatch Queue是 执行处理的等待队列。 dispatch Queue按照追加的顺序执行处理。
另外,在执行处理时候有两种dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue.
dispatch queue的种类 | 说明 |
Serial Dispatch Queue | 等待现在执行中处理结束 |
Concurrent Dispatch Queue | 不等待现在执行中处理结束 |
示意图:
Serial Dispatch Queue :等待处理结束
Concurrent Dispatch Queue:不等待处理结束
serial Dispatch queue 是等待任务一个一个的执行,使用一个线程。
Concurrent Dispatch queue 是不等待执行结束,继续执行下一个,但是还是按照先进来的先执行的顺序来执行任务。
虽然不用等待处理结束可以并行执行多个处理,但并行执行的处理数量取决于当前系统的状态。即iOS基于dispatch Queue 的处理数,CPU核数以及CPU负荷等当前系统的状态来决定Concurrent Dispatch Queue中并行执行的处理数。所谓的并行执行,就是使用多个线程同时执行多个处理。iOS的核心---XUN内核决定应当使用的线程数,并且只生成所需的线程执行处理。当处理结束,XNU内核会结束不再需要的线程。仅需要使用ConCurrent Dispatch Queue就可以完美的管理并行执行多个处理的线程。
比方说在ConCurrent Dispatch Queue中有10个要执行的任务 blk0-blk7,他们在Concurrent Dispatch queue中的线程分配执行情况可能是:
线程0 | 线程1 | 线程2 | 线程3 |
blk0 | blk1 | blk2 | blk3 |
blk4 | blk6 | blk5 | |
blk7 |
假设准备4个Concurrent Dispatch Queue 用线程,首先blk0在线程0中开始执行,接着bl1在线程1 中,blk2在线程2中,blk3在线程3中开始执行。线程0中执行blk0结束后就开始执行blk4,由于线程1中的任务还没有结束,线程2中的任务结束了就开始执行blk5等线程1的任务结束了就开始执行blk6,就这样循环往复。
在Concurrent Dispatch Queue中执行处理时,执行顺序会根据处理内容和系统状态发生改变。它不同于顺序固定的Serial Dispatch Queue 。
把任务追加到Dispatch Queue 中的 函数:同步函数dispatch_sync 和异步函数dispatch_async。
这两种追加任务的函数跟执行任务的两种派发队列,两两组合就有四种方式,外加一种特殊的队列:主队列
追加任务的函数 | 串行队列(Serial Dispatch Queue) | 并发队列(Concurrent Dispatch Queue) | 主队列(Main Dispatch Queue) |
dispatch_sync | 没有开启新线程的能力,同步串行执行,只有一个线程。 | 没有开启新的线程,在当前线程执行。 任务是串行执行的。 | 1:如果是在主线程执行任务,并且把任务追加到主队列中,则追加到主队列中的任务跟主线程当前任务互相等待完成,造成死锁。 2:当前任务不再主线程中执行,或者同步函数追加任务的队列不是主队列,不会产生死锁。 |
dispatch_async | 有开启新的线程的能力,但是执行方式是串行执行,只有一个线程 | 开启新的线程,并且可以多个线程并发执行任务。 | 有开启新线程的能力,但是主队列是串行队列任务会单线程执行 |
由上可知,只有开启多个线程,才能并发执行任务。
代码测试
1 dispatch_async + Serial Dispatch Queue
-(void)TestDispatchQueue
{
dispatch_queue_t concurrentQ =dispatch_queue_create("com.dispatch_concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQ = dispatch_queue_create("com.dispatch_serial_queue", NULL);
NSLog(@"%@",[NSThread currentThread]);
dispatch_async(serialQ, ^{
NSLog(@"任务1");
NSThread * currentThread = [NSThread currentThread];
currentThread.name = @"Test";
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_async(serialQ, ^{
NSLog(@"任务2");
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_async(serialQ, ^{
NSLog(@"任务3");
NSLog(@"%@",[NSThread currentThread]);
});
}
打印结果:
019-03-11 23:02:33.605591+0800 MotherBaby[2585:337807] <NSThread: 0x600001980580>{number = 1, name = main}
2019-03-11 23:02:33.605764+0800 MotherBaby[2585:337848] 任务1
2019-03-11 23:02:33.605961+0800 MotherBaby[2585:337848] <NSThread: 0x6000019ed100>{number = 3, name = Test}
2019-03-11 23:02:33.606055+0800 MotherBaby[2585:337848] 任务2
2019-03-11 23:02:33.606148+0800 MotherBaby[2585:337848] <NSThread: 0x6000019ed100>{number = 3, name = Test}
2019-03-11 23:02:33.606230+0800 MotherBaby[2585:337848] 任务3
2019-03-11 23:02:33.606315+0800 MotherBaby[2585:337848] <NSThread: 0x6000019ed100>{number = 3, name = Test}
可以看出,把任务1所处线程命名为Test ,dispatch_async + Serail Dispatch Queue 的情况下,任务1、2、3是按照他们的添加顺序执行的,并且他们都是在Test线程中执行的,并不是TestDispatchQueue所在的主线程。得出结论:dispatch_async + Serial Dispatch Queue 情况下:开启一个新的线程,所有任务都是在这个新开启的线程串行执行。
2 dispatch_async + Concurrent Dispatch Queue
测试代码:
-(void)TestDispatchQueue
{
dispatch_queue_t concurrentQ =dispatch_queue_create("com.dispatch_concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQ = dispatch_queue_create("com.dispatch_serial_queue", NULL);
NSLog(@"%@",[NSThread currentThread]);
dispatch_async(concurrentQ, ^{
NSLog(@"任务1");
NSThread * currentThread = [NSThread currentThread];
currentThread.name = @"Test";
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_async(concurrentQ, ^{
NSLog(@"任务2");
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_async(concurrentQ, ^{
NSLog(@"任务3");
NSLog(@"%@",[NSThread currentThread]);
});
}
控制台打印的结果是:
2019-03-11 23:08:43.490718+0800 MotherBaby[2636:342396] <NSThread: 0x600001bb9580>{number = 1, name = main}
2019-03-11 23:08:43.490870+0800 MotherBaby[2636:342430] 任务1
2019-03-11 23:08:43.490877+0800 MotherBaby[2636:342434] 任务3
2019-03-11 23:08:43.490873+0800 MotherBaby[2636:342433] 任务2
2019-03-11 23:08:43.491056+0800 MotherBaby[2636:342430] <NSThread: 0x600001bd2f80>{number = 3, name = Test}
2019-03-11 23:08:43.491059+0800 MotherBaby[2636:342434] <NSThread: 0x600001be3a40>{number = 5, name = (null)}
2019-03-11 23:08:43.491077+0800 MotherBaby[2636:342433] <NSThread: 0x600001bd2b40>{number = 4, name = (null)}
从打印结果可以看出:任务1、2、3没有按照他们追加到并发队列中的顺序执行,并且他们所在是三个不同的线程中执行的。
可以得出结论:dispatch_async + Concurrent Dispatch Queue 可以开启多个线程,并且是不会作任何等待,并发的执行任务(先按照添加的顺序吧任务一个一个都开启了,然后再并发的执行,至于那个先完成,不是程序员可控制的。)
3 dispatch_sync + Serial Dispatch Queue (并非主队列)
测试代码:
-(void)TestDispatchQueue
{
dispatch_queue_t concurrentQ =dispatch_queue_create("com.dispatch_concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQ = dispatch_queue_create("com.dispatch_serial_queue", NULL);
NSLog(@"%@",[NSThread currentThread]);
dispatch_sync(serialQ, ^{
NSLog(@"任务1");
NSThread * currentThread = [NSThread currentThread];
currentThread.name = @"Test";
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(serialQ, ^{
NSLog(@"任务2");
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(serialQ, ^{
NSLog(@"任务3");
NSLog(@"%@",[NSThread currentThread]);
});
}
控制台打印情况:
-(void)TestDispatchQueue
{
dispatch_queue_t concurrentQ =dispatch_queue_create("com.dispatch_concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQ = dispatch_queue_create("com.dispatch_serial_queue", NULL);
NSLog(@"%@",[NSThread currentThread]);
dispatch_sync(serialQ, ^{
NSLog(@"任务1");
NSThread * currentThread = [NSThread currentThread];
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(serialQ, ^{
NSLog(@"任务2");
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(serialQ, ^{
NSLog(@"任务3");
NSLog(@"%@",[NSThread currentThread]);
});
}
控制台打印情况:
2019-03-11 23:19:44.048480+0800 MotherBaby[2740:350702] <NSThread: 0x6000012c8d00>{number = 1, name = main}
2019-03-11 23:19:44.048644+0800 MotherBaby[2740:350702] 任务1
2019-03-11 23:19:44.048759+0800 MotherBaby[2740:350702] <NSThread: 0x6000012c8d00>{number = 1, name = main}
2019-03-11 23:19:44.049267+0800 MotherBaby[2740:350702] 任务2
2019-03-11 23:19:44.051100+0800 MotherBaby[2740:350702] <NSThread: 0x6000012c8d00>{number = 1, name = main}
2019-03-11 23:19:44.051190+0800 MotherBaby[2740:350702] 任务3
2019-03-11 23:19:44.051287+0800 MotherBaby[2740:350702] <NSThread: 0x6000012c8d00>{number = 1, name = main}
在 Dispatch_sync + Serial Dispatch Queue (非主队列)不会死锁。没有开启新的线程,实在主线程中按照添加的顺序执行。
再来看一种会死锁的情况:
dispatch_sync(serialQ, ^{
NSLog(@"任务2");
dispatch_sync(serialQ, ^{//会报告EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)错误
NSLog(@"执行");
});
NSLog(@"%@",[NSThread currentThread]);
});
这种情况下执行会报错:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)。
这时候由于串行派发队列派发的特点:一个执行完毕才会派发另一个,于是发生死锁。
GCD中发生死锁需要满足三个条件:
1:任务A和任务B先后追加到同一个串行队列中(B需要等待A完成)
2:任务B完成是任务A完成的前提条件
3:任务B是同步添加到串行队列中的(2,3导致阻塞,A需要等待B完成)
这样在同一个串行队列中互相等待形成,死锁发生。其实2,3可以合并为一个因素,2就是3导致的结果。
解释:只有串行队列才会有死锁的情况,发生死锁就是说有任务互相等待完成,根据串行队列的特点,任务A先被添加到队列,所以A先执行,A执行完毕之后B才会执行(B等待A)。但是 B 是同步追加到串行队列,意味着在B的block 任务完成之前,是一直等待的状态,也就是B完不成,A就无法完成(A等待B)(如果是异步添加B到串行队列,不会做任何等待,A是可以完成的),而在当前串行队列中,A排在B前面,A执行完才能执行B,这样造成互相等待。B 等待A完成是串行队列的特点造成的,A等待B是因为同步函数造成的。如果把B任务换成异步添加,就不会有死锁的情况发生。
示例代码:以上三个条件任一不满足都不会死锁。
//假设当前执行方法在主线程执行。
dispatch_queue_t queue = dispatch_queue_create("asdsadsadas", NULL);
/*
任务A 跟任务B不再同一个串行队列,不会造成死锁
*/
dispatch_sync(queue, ^{
NSLog(@"adasd");
NSLog(@"当前=%@",[NSThread currentThread]);
});
/*任务B的完成不是任务A的完成条件,不用说,没什么关系肯定不会死锁*/
/*任务B不是同步添加到串行队列中的,不会造成死锁*/
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"asdasd");
});
4. dispatch_sync + Concurrent Dispatch Queue
示例代码:
dispatch_sync(concurrentQ, ^{
NSLog(@"任务1");
NSThread * currentThread = [NSThread currentThread];
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQ, ^{
NSLog(@"任务2");
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQ, ^{
NSLog(@"任务3");
NSLog(@"%@",[NSThread currentThread]);
});
控制台打印情况:
2019-03-12 00:12:47.877788+0800 MotherBaby[3422:395875] <NSThread: 0x600003f05900>{number = 1, name = main}
2019-03-12 00:12:47.877929+0800 MotherBaby[3422:395875] 任务1
2019-03-12 00:12:47.878031+0800 MotherBaby[3422:395875] <NSThread: 0x600003f05900>{number = 1, name = main}
2019-03-12 00:12:47.878117+0800 MotherBaby[3422:395875] 任务2
2019-03-12 00:12:47.878241+0800 MotherBaby[3422:395875] <NSThread: 0x600003f05900>{number = 1, name = main}
2019-03-12 00:12:47.878317+0800 MotherBaby[3422:395875] 任务3
2019-03-12 00:12:47.878402+0800 MotherBaby[3422:395875] <NSThread: 0x600003f05900>{number = 1, name = main}
可以看出,是顺序执行的,没有开启新的线程,在主线程完成。
另外,dispatch_sync + Serial Dispatch Queue(不会开启新的线程,只在当前线程串行执行任务) 可以保证线程同步。
示例代码:
dispatch_sync(self.serialQueue, ^{
if(self.count>0)
{
self.count--;
NSLog(@"%ld",self.count);
NSLog(@"当前访问窗口:%@",[NSThread currentThread]);
}
});
dispatch_sync ,保证在当前线程执行,serialQueue 是串行队列,上一次派发的任务执行完毕才会执行再一次的派发。从而保证了线程的同步。
开启线程是追加函数的功能,怎么执行,在哪个线程中执行是派发队列的功能。串行派发队列 Serial Dispatch Queue 派发任务到线程的方式是一个一个派发,并且派发的一个任务完成之后才会派发第二个任务。并发派发队列 Concurrent Dispatch Queue 派发任务到多个线程的方式是一个一个派发,但是不会等待完成。
创建Dispatch Queue
创建一个Dispatch Queue有两种方法。第一种是使用GCD 的API dispathc_queue_creat 创建。
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd", NULL);
(一般用到Serial Dispatch Queue ,是在避免多个线程更新相同资源引发数据竞争时使用)
第一个参数是指定Serial Dispatch Queue 的名称,推荐使用应用程序ID逆序全域名的方式。第二个参数设置为NULL。生成Concurrent Dispatch Queue时候,第二个参数设置为“DISPATCH_QUEUE_CONCURRENT”,返回值为dispatch_queue_t 类型。
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.concurrentdispatchqueue", DISPATCH_QUEUE_CONCURRENT);
第二种方法是获取系统标准提供的Dispatch Queue:Main Dispatch Queue 和 Gloable Dispatch Queue。
Main Dispatch Queue是在主线程中执行的 Dispatch Queue ,因为主线程只有一个,所以 Main Dispatch Queue 是Serial Dispatch Queue 的一种特殊形式。追加到Main Diapatch Queue中的处理在主线程的RunLoop中执行。由于在主线程使用,所以一些更新UI界面的操作需要追加到Main Dispatch Queue中执行。这与NSObject 方法 performSeletchOnMainThread 相同。
Global Dispatch Queue 是所有程序都能使用的 Concurrent Dispatch Queue。只要获取Global Dispatch Queue即可。Global Dispatch Queue 有四个优先级:高优先级(High Priority )、默认优先级(Default Priority)、低优先级(Low Priority)、后台优先级(Background Priority)。在向Global Dispatch Queue 中添加应用的时候,应该选择与之对应的优先级。
各种Dispatch Queue 的获取方法:
/*获取主队列*/
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
/*获取全局队列*/
dispatch_queue_t globalQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
GCD的一些函数:
1:dispatch_set_target_queue
dispatch_queue_creat 函数生成的Dispatch Queue不管是串行队列还是并行队列他们都使用与Global Dispatch Queue 相同执行优先级的线程,想要变更生成的Dispatch Queue 的执行优先级要使用dispatch_set_target_queue函数。例如,创建一个在后台执行动作处理的Serial Dispatch Queue 的生成方法如下:
//创建一个串行队列
dispatch_queue_t serialqueue = dispatch_queue_create("mySerialDispatchQueue", NULL);
//创建一个后台执行全局队列
dispatch_queue_t defaultGlobalqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//将串行队列z转化为在后台执行
dispatch_set_target_queue(serialqueue, defaultGlobalqueue);
第一个参数是指定需要变更执行优先级的Dispatch Queue,第二个参数是与 要指定的执行优先级同优先级的全局队列。第一个参数不可以指定为主队列和全局队列。
(这个函数只理解这么多,也不是常用)
2 dispatch_after
经常会有这样的情况:想在3秒后执行处理,可能不仅限欲3秒,总之,这种想在指定时间后执行处理的情况,可以使用dispatch_after 函数来实现。
3秒后把指定的任务追加到Main Dispatch Queue 中的代码:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"等待三秒后追加当前任务到主队列");
});
需要注意的是,dispatch_after 并不是在指定时间后执行处理,而是在指定时间后追加处理到Dispatch Queue,此源代码与3秒后
用dispatch_async 函数追加任务到主队列相同。
因为Main Dispatch Queue 是在主线程的RunLoop中执行,所以在比如每隔1/60秒执行的runLoop 中block 在最快3秒执行,最慢在3+1/60秒后执行并且在Main Dispatch Queue 中有大量的处理追加或主线程的处理本身有延时,这个时间会更长。
所以,有严格时间要求的时候使用这个函数会出问题,但是想大致的延时时间,这个函数是非常有效的。
3 Dispatch Group
常见一个场景:在追加到Dispatch Queue 中的多个处理全部结束的时候想执行结束处理。如果使用一个串行队列 Serial Dispatch Queue ,则由于是串行执行,所以待队列执行结束直接做结束处理就可以。但是如果是使用Concurrent Dispatch Queue 或者使用多个Dispatch Queue 的时候,情况就会复杂。
这时候就可以时候用Dispatch Group 。一下示例:追加三个任务到全局队列,他们会在异步线程并发执行,等到这些任务执行完毕就会执行Main Dispatch Queue 中的结束处理。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group =dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
因为是向全局并发队列中添加任务,所以任务的执行完成的顺序是不定的,但是“更新UI”一定是最后执行。
2019-03-13 12:23:15.201994+0800 MotherBaby[7414:793663] blk3
2019-03-13 12:23:15.201990+0800 MotherBaby[7414:793664] blk0
2019-03-13 12:23:15.201990+0800 MotherBaby[7414:793662] blk1
2019-03-13 12:23:15.202011+0800 MotherBaby[7414:793659] blk2
2019-03-13 12:23:15.213217+0800 MotherBaby[7414:793632] 更新UI
无论向什么Dispatch Queue 中追加处理,Dispatch Queue 都能够监视这些处理执行的结束,一旦检测到所有处理执行结束,就可以将结束处理追加到Dipatch Queue 中。
dispatch_group_notify 函数第一个参数是监视的group,使用dipatch_group_notify 函数将block 中的任务追加到第二个参数指定的queue中,在追加的时候,dipatch_group中的所有任务都已经结束。
另外,dispatch_group_wait 函数,可以做到等待group任务全部执行结束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group =dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk3");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_wait 函数第二个参数指定为等待时间是属于dispatch_time_t 类型的值, DISPATCH_TIME_FOREVER 是永久等待
只要group 中的处理没有完成,就会永久等待,中途不能取消。
比如我指定等待时间为1秒,
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if(result==0)
{
//属于dispatch Group 的全部处理执行结束
}
else
{
//属于dispatch Group 的某一个处理还没有结束
}
如果dispaatch_group_wait 函数返回的值不为0 ,则说明当前等待时间结束后group 中有任务还没有完成。
4 dispatch_barrier_async
dispatch_barrier_async 可以实现高效率的数据库或者文件访问。Serial Dispatch Queue可以避免数据竞争的问题。
数据库操作,写入处理不可以与其他的写入处理以及保护读取处理的其他某些操作并行执行,但是读取操作与读取操作并行执行是没有问题的。
会等待前面的并行执行的处理结束之后,再将指定的处理追加到queue中。
5 dispatch_apply
dispatch_apply函数是按照指定的次数将指定的block追加到指定dispatch Queue中,并且等待全部处理执行结束。
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"Done");
输出结果:
2019-03-13 16:40:06.934744+0800 MotherBaby[9670:983147] 2
2019-03-13 16:40:06.934739+0800 MotherBaby[9670:983116] 0
2019-03-13 16:40:06.934744+0800 MotherBaby[9670:983149] 1
2019-03-13 16:40:06.934764+0800 MotherBaby[9670:983148] 3
2019-03-13 16:40:06.934904+0800 MotherBaby[9670:983116] 5
2019-03-13 16:40:06.934904+0800 MotherBaby[9670:983147] 4
2019-03-13 16:40:06.934980+0800 MotherBaby[9670:983149] 6
2019-03-13 16:40:06.934980+0800 MotherBaby[9670:983116] 7
2019-03-13 16:40:06.934982+0800 MotherBaby[9670:983148] 8
2019-03-13 16:40:06.935044+0800 MotherBaby[9670:983147] 9
2019-03-13 16:40:06.935488+0800 MotherBaby[9670:983116] Done
如果添加到并发队列,各个执行时间的不定的,所以输出是无序的,如果添加到串行队列,输出就是0-9。但是Done 一定是最后执行。
另外dispatch_apply 函数跟dispatch_sync 一样,会等待处理结束,因此在dispatch_asycn函数中执行dispatch_apply。
NSArray * arr = @[@"ds",@"d",@"d",@"d",@"d"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_apply([arr count], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
NSLog(@"%zu",index
);
//NSLog(@"%@",[arr objectAtIndex:index]);
});
});
dispatch_async(dispatch_get_main_queue(), ^{
//更新UI操作
NSLog(@"更新UI操作");
});
6 dispatch__suspend /dispatch_resume
当追加大量处理到dispatch Queue 时候,在追加处理的过程中,有时候希望不执行已经追加的处理。这种情况下只要挂起dispatch Queue 即可,当可以执行时候再恢复。
dispatch_suspend 函数挂起指定的dispatch queue.
dispatch_queue_t queue = dispatch_queue_create("dispatchqueueCreat", DISPATCH_QUEUE_CONCURRENT);
dispatch_suspend(queue);
dispatch_resume 函数指定恢复某个被挂起的函数
dispatch_resume(queue);
这些操作对已经执行的操作没有影响,挂起后,追加到dispatch queue 中但是尚未执行的处理在此之后停止执行,dispatch_resume 可以唤起继续执行。(正在执行的怎么办呢?估计也是停止执行)。
7 Dispatch Semaphore
dispatch Semaphore 可以做到线程安全。
Dispatch Semaphore 是带有计数的信号量,该计数是多线程编程中的技术类型信号。 使用计数来实现该功能。计数为0 的时候等待,计数为1或者大于1的时候就渐去1 不等待,继续执行。
示例代码:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray * array= [[NSMutableArray alloc]init];
for (int i = 0; i<10000; i++) {
dispatch_async(queue, ^{
[array addObject:[NSNumber numberWithInt:i]];
});
}
以上代码很大概率会发生内存错误。
使用dispatch_semaphore_signal 处理线程安全的问题
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore =dispatch_semaphore_create(1);
NSMutableArray * array= [[NSMutableArray alloc]init];
for (int i = 0; i<10000; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER );
[array addObject:[NSNumber numberWithInt:i]];
dispatch_semaphore_signal(semaphore);
});
}
这样就很好的保证了同一时间只有一个线程访问数组。
8 dipatch_once
dispatch_once函数是保证某一段代码只执行一次。
如果保证某一段代码只执行一次,通常可以这么写:
if(isfirst == YES)
{
//执行操作
isfirst = NO;
}
如果使用dispatch_once 来处理,是这样:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//处理一些操作
});
区别就是dispatch_once不仅保证只执行一次,还保证在多线程环境下也是百分之百安全。这就是所谓的单利模式。
完整的用dispatch_once 来写一个单例是这样的: