iOS多线程——GCD
在iOS中,实现多线程的方式有很多,比如GCD,NSOperation,NSThread等等,但是一直对线程的概念模糊,今天就根据代码例子来了解iOS中GCD的用法和原理。
GCD是和block紧密相连的,所以最好先了解下block。
GCD是C level的函数,这意味着它也提供了C的函数指针作为参数,方便了C程序员。
下面首先来看GCD的使用:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async表明异步运行,block代表的是你要做的事情,queue则是你把任务交给谁来处理了。(除了async,还有sync,delay,本文以async为例)。
dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾。
之所以程序中会用到多线程是因为程序往往会需要读取数据,然后更新UI。为了良好的用户体验,读取数据的操作会倾向于在后台运行,这样以避免阻塞主线程。GCD里就有三种queue来处理。
GCD Queue分为三种:
- The main queue:主队列,主线程就是在个队列中,系统默认就有一个串行队列
- Global queues: 全局并发队列
- 用户队列:是用函数dispatch_queue_create创建的自定义队列
而用户队列又分为下面两种:
(1)Serial Dispatch Queue
线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。(DISPATCH_QUEUE_SERIAL)
(2)Concurrent Dispatch Queue
线程池提供多个线程来执行任务,所以可以按序启动多个任务并发执行。(DISPATCH_QUEUE_CONCURRENT)
关于同步和异步
-
dispatch_sync
则调用用 dispatch_sync的线程会等dispatch_sync的对内容执行完再继续执行。dispatch_sync函数不会立即返回,即阻塞当前线程,等待block同步执行完成。 -
dispatch_async
调用dispatch_async的线程不会的等dispatch_async的内容,自己继续执行。dispatch_async函数会立即返回, block会在后台异步执行。
关于并发和并行
-
并行:是多个任务在同一个时间片段内同时进行,比如说你一边吃饭一边打电话一遍看电视
-
并发:是多个任务在同一个时间的点上可以切换进行,比如说你先吃着饭,然后电话来了,停下吃饭立马打电话,打完后立马切换到看电视,这个你在同一个线条内只有一个任务在执行
图示关于并发和并行
并行 并发下面就以代码实例来分析GCD用法
实例一:DISPATCH_QUEUE_SERIAL串行队列
/**
DISPATCH_QUEUE_SERIAL是每次运行一个任务,可以添加多个,执行次序FIFO。
**/
- (void)test1
{
NSDate *date = [NSDate date];
NSString *daStr = [date description];
const char *queueName = [daStr UTF8String];
//DISPATCH_QUEUE_SERIAL等同于NULL
dispatch_queue_t myQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(addTime) userInfo:nil repeats:YES];
//在main_queue中异步将任务放到myQueue队列里
//放入myQueue的顺序是按代码的执行顺序:任务一,任务二,任务三(因为main_queue是顺序执行)
//在myQueue取出的顺序是按照FIFO的顺序
//因为这是一个串行的队列,所以取出后的任务是一个接着一个执行的
//执行结果
//[NSThread sleepForTimeInterval:6]~~~~~~~~6
//[NSThread sleepForTimeInterval:3]~~~~~~~~9
//[NSThread sleepForTimeInterval:12]~~~~~~~~21
//任务一:
dispatch_async(myQueue, ^{
[NSThread sleepForTimeInterval:6];
NSLog(@"[NSThread sleepForTimeInterval:6]~~~~~~~~%d", t);
});
//任务二:
dispatch_async(myQueue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"[NSThread sleepForTimeInterval:3]~~~~~~~~%d", t);
});
//任务三:
dispatch_async(myQueue, ^{
[NSThread sleepForTimeInterval:12];
NSLog(@"[NSThread sleepForTimeInterval:12]~~~~~~~~%d", t);
});
}
-(void)addTime {
++t;
}
实例二:全局的并发队列dispatch_get_global_queue
/**
可以同时运行多个任务,每个任务的启动时间是按照加入queue的顺序,结束的顺序依赖各自的任务.
**/
- (void)test2
{
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//在main_queue中异步将任务放到myQueue
//放入myQueue的顺序是:任务一,任务二,任务三
//在myQueue中取出的顺序是FIFO的原则:任务一,任务二,任务三
//取出的任务放到线程里执行,因为这是一个并行的队列,所以任务可以同时运行
//执行的结果
//执行的结果可以是 1 2 3 的随机组合
//任务一:
dispatch_async(myQueue, ^{
NSLog(@"~~~~~~~~~1");
});
//任务二:
dispatch_async(myQueue, ^{
NSLog(@"~~~~~~~~~2");
});
//任务三:
dispatch_async(myQueue, ^{
NSLog(@"~~~~~~~~~3");
});
}
实例三:DISPATCH_QUEUE_SERIAL串行队列
/**
串行队列的异步任务:使用一个子线程依次执行。
对比一下dispatch_async和dispatch_sync输出的i的顺序和线程的地址
**/
- (void)test3
{
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 3; i++) {
//在main_queue中将i=0,1,2的任务异步加入queue队列中
//加入queue的顺序是:i=0, i=1, i=2
//所以在queue中取出的顺序是:i=0,i=1,i=2
//因为queue是同步队列,所以三个任务放到同一个线程中依次执行
//运行结果
//~~~i=0~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
//<NSThread: 0x600000068d00>{number = 1, name = (null)}
//~~~i=1~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
//<NSThread: 0x600000068d00>{number = 1, name = (null)}
// ~~~i=2~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
//<NSThread: 0x600000068d00>{number = 1, name = (null)}
//当是使用DISPATCH_QUEUE_SERIAL和dispatch_sync同步的时候,三个任务执行的线程就是当前的mainThread中执行的
dispatch_async(queue, ^{
NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
NSLog(@"%@",[NSThread mainThread]);
});
}
}
实例四:DISPATCH_QUEUE_CONCURRENT并发队列
/**
并行队列的异步任务:使用多个子线程无序执行,一般任务较少时几个任务就开几个线程,较多时则开部分线程。
应用:一系列的异步任务没有先后顺序,结束无序
对比一下dispatch_async和dispatch_sync输出的线程的地址
**/
- (void)test4
{
dispatch_queue_t queue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
//在main_queue中异步的将i=0,i=1,i=2的任务加入到queue中
//加入queue的顺序是i=0,i=1,i=2
//在queue中取出的顺序是i=0,i=1,i=2
//因为queue是并发队列,所以有多条的线程来执行三个任务,thread的执行的顺序不定
//当使用dispatch_sync时,执行的顺序又成了i=0,i=1,i=2
for (int i = 0; i < 3; i++) {
dispatch_async(queue, ^{
NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
NSLog(@"%@",[NSThread mainThread]);
});
}
}
实例五:dispatch_group_t组队列
/**
dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。
dispatch_group_async会监听最终的任务完成后,并通知一个线程
这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成了。
注意这里不是监听queue里所有的任务完成,而是添加到组里的任务,这个任务是在这个queue里,同时也在这个组里,组里所有的任务的完成并不代表queue里所有的任务的完成。
下面是一段例子代码:
注意:当queue是global(或者DISPATCH_QUEUE_CONCURRENT)队列和DISPATCH_QUEUE_SERIAL队列时线程的区别
**/
- (void)test5
{
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue = dispatch_queue_create("queue3", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"task1~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"task2~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"task3~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"回到主线程");
});
}
实例六:dispatch_apply重复
/**
dispatch_apply:执行某个代码片段N次
重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。
多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
运行结果:
array[0]~~~~~~~~~~~~~~0
array[3]~~~~~~~~~~~~~~3
array[2]~~~~~~~~~~~~~~2
array[1]~~~~~~~~~~~~~~1
array[4]~~~~~~~~~~~~~~4
array[5]~~~~~~~~~~~~~~5
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
运行结果:
array[0]~~~~~~~~~~~~~~0
array[1]~~~~~~~~~~~~~~1
array[2]~~~~~~~~~~~~~~2
array[3]~~~~~~~~~~~~~~3
array[4]~~~~~~~~~~~~~~4
array[5]~~~~~~~~~~~~~~5
**/
- (void)test6
{
NSArray *array = @[@"0",@"1",@"2",@"3",@"4",@"5"];
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
//打印done是在dispatch_apply里所有的任务执行完毕之后才会执行
dispatch_async(queue2, ^(){
//放在dispatch_apply里面的任务的执行顺序完全依赖于queue1的队列串发还是并发
dispatch_apply([array count], queue1, ^(size_t index) {
NSLog(@"array[%ld]~~~~~~~~~~~~~~%@", index, array[index]);
});
NSLog(@"done");
});
}
实例七:dispatch_barrier_async
/**
dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
只有当这个队列为自己创建的并发队列(DISPATCH_QUEUE_CONCURRENT)时才会有这种效果
执行结果(一)为:
dispatch_async2
dispatch_async1
dispatch_barrier_async
dispatch_async3
如果使用dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
根据官方文档指出,这个时候的dispatch_barrier_async完全等同于dispatch_async
执行结果(二)为:
dispatch_barrier_async
dispatch_async3
dispatch_async2
dispatch_async1
在使用DISPATCH_QUEUE_SERIAL串行队列时,完全就按照FIFO的顺序执行了
执行结果(三)为:
2015-12-23 16:53:04.574 NSURLDemo[67716:3661098] dispatch_async1
2015-12-23 16:53:05.577 NSURLDemo[67716:3661098] dispatch_async2
2015-12-23 16:53:05.578 NSURLDemo[67716:3661098] dispatch_barrier_async
2015-12-23 16:53:07.085 NSURLDemo[67716:3661098] dispatch_async3
不仅有dispatch_barrier_async方法,还有dispatch_barrier_sync方法,两个方法的区别是:
dispatch_barrier_async当他把这个barrier添加到队列后,当前队列不用等待block的执行返回
而dispatch_barrier_sync需要等待block的内容执行完毕之后再继续下面的执行
**/
- (void)test7
{
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"dispatch_async1~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async2~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
});
dispatch_barrier_async(queue, ^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"dispatch_barrier_async~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
});
}
实例八:dispatch_once一次使用函数
/**
dispatch_once
dispatch_once这个函数,它可以保证整个应用程序生命周期中某段代码只被执行一次!
**/
- (void)test8
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
}
实例九:dispatch_after延迟函数
/**
dispatch_after
有时候我们需要等个几秒钟然后做个动画或者给个提示,这时候可以用dispatch_after这个函数:
**/
-(void)test9
{
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
}
实例十:dispatch_set_target_queue转换队列
/**
dispatch_set_target_queue
通过dispatch_set_target_queue函数可以设置一个dispatch queue的优先级,或者指定一个dispatch source相应的事件处理提交到哪个queue上。
它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB);
那么dispatchA上还未运行的block会放到dispatchB上,然后由dispatchB来进行管理运行。
**/
dispatch_set_target_queue(serialQ, globalQ);
实例十一:dispatch_group_wait等待
//dispatch_group_wait来等待这些任务完成。若任务已经全部完成或为空,则直接返回,否则等待所有任务完成后返回。
//dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
-(void)test10
{
dispatch_group_t group = dispatch_group_create();
//注意queue为DISPATCH_QUEUE_CONCURRENT 和 DISPATCH_QUEUE_SERIAL时当前的线程
dispatch_queue_t queue = dispatch_queue_create([@"com.queue" UTF8String], DISPATCH_QUEUE_SERIAL);
//每个dispatch_group_enter对应一个dispatch_group_leave完成group内所有的任务则发送通知
//进入group
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task1~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
//离开group
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task2~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
dispatch_group_leave(group);
});
NSLog(@"~~~~~~~~~~~~~~~~~~before group wait");
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"~~~~~~~~~~~~~~~~~~after group wait");
dispatch_group_notify(group, queue, ^{
NSLog(@"~~~~~~~~~~~~~allGroupFinish");
});
}
补充:dispatch_sync(dispatch_get_main_queue(),...)造成死锁的原因
当这段代码放在主线程里,也即dispatch_get_main_queue()中,执行到sync时向dispatch_get_main_queue()插入同步thread,sync会等到里面的block执行完成才返回。sync又在主队列里面,是个串行队列,sync是后面才加入的,前面一个是主线程,所以sync想执行block必须等待前一个主线程执行完成,而主线程却在等待sync返回,才能执行后续工作,从而造成死锁。