iOS 进阶iOS DeveloperiOS模块详解

iOS多线程——GCD

2017-02-27  本文已影响371人  未之

在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分为三种:

而用户队列又分为下面两种:
(1)Serial Dispatch Queue
线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。(DISPATCH_QUEUE_SERIAL)

(2)Concurrent Dispatch Queue
线程池提供多个线程来执行任务,所以可以按序启动多个任务并发执行。(DISPATCH_QUEUE_CONCURRENT)

关于同步和异步

关于并发和并行

图示关于并发和并行

并行 并发

下面就以代码实例来分析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返回,才能执行后续工作,从而造成死锁。

上一篇下一篇

猜你喜欢

热点阅读