IOS---多线程实现方案二 (GCD)

IOS---多线程实现方案二 (GCD)

上篇文章讨论了使用PTHread和NSThread的多线程实现。这篇文章,我们讨论一下在开发中最常用的GCD的使用。

1. 基本概念

1.1 什么是GCD

GCD是Grand Central Dispatch的简称,是一套基于C语言的底层API。使用GCD,我们不需要编写线程代码,其生命周期也不需要我们手动管理。只需要定义想要执行的任务,然后添加到适当的调度队列,也就是dispatch queue。GCD会负责创建线程和调度任务,系统直接提供线程管理。

1.2 任务和队列

在GCD中,有两个核心的概念分别是:任务队列

  • 任务:执行什么操作,在GCD中即是block。
    任务有两种执行方式:同步(sync)异步(async)
    同步(sync):阻塞当前线程并等待当前任务执行,执行完毕后继续往下运行。
    异步(async):不阻塞当前线程。
  • 队列:用于存放任务,有两种队列:串行队列(Serial Queue)并行队列(Concurrent Queue)
    串行队列(SerialQueue):串行队列中的任务会根据队列的定义先进先出(FIFO)。
    如果有不明白的读者,可以理解为在高速行驶的汽车,在经过收费站时进行了排队,先进入收费站的汽车先交费先离开收费站。而排在最后的汽车最后进入收费站,最后离开。
    串行队列图

    并行队列(Concurrent Queue):在并行队列中的任务,GCD也会根据队列定义,FIFO的取出来。但与串行队列不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
    并行队列图

1.3 队列类型

队列分为两类:系统提供自己创建

  • 自己创建队列: 我们可以通过dispatch_queue_create来自行创建队列进行使用。可以创建串行队列并行队列两种,以下是队列的创建方式。
//第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列。
//第二个参数用来表示创建的队列是串行的还是并行的。
dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>)
// 创建串行队列
dispatch_queue_t mySerialQueue = dispatch_queue_create("com.ivanding.serial", DISPATCH_QUEUE_SERIAL);
// 创建并行队列
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.ivanding.concurrent", DISPATCH_QUEUE_CONCURRENT);
  • 系统提供:
    1.系统提供的串行队列:
// 主队列,主线程中的唯一队列,串行队列
dispatch_get_main_queue()

注意:
(1).主队列是串行队列,所以任务只能一个一个执行。
(2).主队列中的所有任务都在主线程执行。
(3).主线程是唯一可用于更新UI的线程,所以所有有关UI操作的任务都要在主队列中。
2.系统提供的并行队列:

// 全局并行队列
dispatch_get_global_queue
long identifier:ios 8.0 告诉队列执行任务的“服务质量 quality of service”,系统提供的参数有:
     QOS_CLASS_USER_INTERACTIVE 0x21,              用户交互(希望尽快完成,用户对结果很期望,不要放太耗时操作)
     QOS_CLASS_USER_INITIATED 0x19,                用户期望(不要放太耗时操作)
     QOS_CLASS_DEFAULT 0x15,                        默认(不是给程序员使用的,用来重置对列使用的)
     QOS_CLASS_UTILITY 0x11,                        实用工具(耗时操作,可以使用这个选项)
     QOS_CLASS_BACKGROUND 0x09,                     后台
     QOS_CLASS_UNSPECIFIED 0x00,                    未指定
     iOS 7.0 之前 优先级
     DISPATCH_QUEUE_PRIORITY_HIGH 2                 高优先级
     DISPATCH_QUEUE_PRIORITY_DEFAULT 0              默认优先级
     DISPATCH_QUEUE_PRIORITY_LOW (-2)               低优先级
     DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后台优先级
// 获取默认优先级的全局并行队列,这里dispatch_get_global_queue的第一个参数为优先级,第二个参数是苹果为未来预留的参数,这里默认写0就可以了
dispatch_queue_t globalQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);

1.4 使用GCD开启线程

使用GCD开启的线程有两种: 同步异步

// 同步函数,将block中的任务添加到队列中同步执行
dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
// 异步函数,将block中的任务添加到队列中异步执行
dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)

同步线程是不具备创建子线程的能力的,一般情况下,同步线程的任务会在唯一的主线程中执行,也就是说唯一的(main)主线程就成了同步线程。而异步线程是具备创建子线程的能力的,一般情况下,异步线程就是(main)主线程以外的线程,不唯一,会有多个,具体多少个,具体是哪些线程,如果是通过GCD创建,全部由GCD决定。
为了印证这个说法,这里给出demo以及代码片段:

  • 异步函数 + 串行队列
/**
 *  异步函数 + 串行队列
 */
- (void)asyncSerialQueue {
    NSLog(@"%s", __func__);
    // 自己创建的串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ivanding.serial", DISPATCH_QUEUE_SERIAL);
    // 异步函数执行任务
    dispatch_async(queue, ^{
        NSLog(@"asyncSerial1---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncSerial2---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncSerial3---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncSerial4---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncSerial5---%@", [NSThread currentThread]);
    });
}
异步函数 + 串行队列输出结果
  • 异步函数 + 主线程串行队列
/**
 *  异步函数 + 主线程串行队列
 */
- (void)asyncMainSerialQueue {
    NSLog(@"%s", __func__);
    // 获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    // 异步函数执行任务
    dispatch_async(queue, ^{
        NSLog(@"asyncMainSerial1---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncMainSerial2---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncMainSerial3---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncMainSerial4---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncMainSerial5---%@", [NSThread currentThread]);
    });
}
异步函数 + 主线程串行队列输出结果
  • 异步函数 + 异步队列
/**
 *  异步函数 + 异步队列
 */
- (void)asyncConcurrentQueue {
    NSLog(@"%s", __func__);
    // 自己创建的异步队列
    dispatch_queue_t queue = dispatch_queue_create("com.ivanding.concurrent", DISPATCH_QUEUE_CONCURRENT);
    // 异步函数执行任务
    dispatch_async(queue, ^{
        NSLog(@"asyncConcurrent1---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncConcurrent2---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncConcurrent3---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncConcurrent4---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncConcurrent5---%@", [NSThread currentThread]);
    });
}
异步函数 + 异步队列输出结果
  • 异步函数 + 全局异步队列
/**
 *  异步函数 + 全局异步队列
 */
- (void)asyncGlobalConcurrentQueue {
    NSLog(@"%s", __func__);
    // 获取全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 异步函数执行任务
    dispatch_async(queue, ^{
        NSLog(@"asyncGlobalConcurrent1---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncGlobalConcurrent2---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncGlobalConcurrent3---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncGlobalConcurrent4---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"asyncGlobalConcurrent5---%@", [NSThread currentThread]);
    });
}
异步函数 + 全局异步队列输出结果
  • 同步函数 + 串行队列
/**
 *  同步函数 + 串行队列
 */
- (void)syncSerialQueue {
    NSLog(@"%s", __func__);
    // 自己创建的串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ivanding.serial", DISPATCH_QUEUE_SERIAL);
    // 同步函数执行任务
    dispatch_sync(queue, ^{
        NSLog(@"syncSerial1---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncSerial2---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncSerial3---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncSerial4---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncSerial5---%@", [NSThread currentThread]);
    });
}
同步函数 + 串行队列输出结果
  • 同步函数 + 主线程串行队列
/**
 *  同步函数 + 主线程串行队列
 */
- (void)syncMainSerialQueue {
    NSLog(@"%s", __func__);
    // 获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    // 同步函数执行任务
    dispatch_sync(queue, ^{
        NSLog(@"syncMainSerial1---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncMainSerial2---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncMainSerial3---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncMainSerial4---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncMainSerial5---%@", [NSThread currentThread]);
    });
}
同步函数 + 主线程串行队列输出结果
  • 同步函数 + 异步队列
/**
 *  同步函数 + 异步队列
 */
- (void)syncConcurrentQueue {
    NSLog(@"%s", __func__);
    // 自己创建的异步队列
    dispatch_queue_t queue = dispatch_queue_create("com.ivanding.concurrent", DISPATCH_QUEUE_CONCURRENT);
    // 同步函数执行任务
    dispatch_sync(queue, ^{
        NSLog(@"syncConcurrent1---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncConcurrent2---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncConcurrent3---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncConcurrent4---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncConcurrent5---%@", [NSThread currentThread]);
    });
}
同步函数 + 异步队列输出结果
  • 同步函数 + 全局异步队列
/**
 *  同步函数 + 全局异步队列
 */
- (void)syncGlobalConcurrentQueue {
    NSLog(@"%s", __func__);
    // 获取全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 同步函数执行任务
    dispatch_sync(queue, ^{
        NSLog(@"syncGlobalConcurrent1---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncGlobalConcurrent2---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncGlobalConcurrent3---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncGlobalConcurrent4---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"syncGlobalConcurrent5---%@", [NSThread currentThread]);
    });
}
同步函数 + 全局异步队列输出结果

下图是对以上的执行情况的总结:


各种队列的执行效果

1.5 使用GCD进行线程间通信

我们使用同NSThread一样的下载图片的例子来演示使用GCD进行线程间通信。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 下载图片
    [self downloadImage];
}

/**
 *  下载图片
 */
- (void)downloadImage {
    // 获取默认优先级的全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 使用异步函数
    dispatch_async(queue, ^{
        // 获取url
        NSURL *url = [NSURL URLWithString:@"http://p5.img.cctvpic.com/20100107/images/1262848505292_1262848505292_r.jpg"];
        // 从url获取二进制数据(下载图片)
        NSData *data = [NSData dataWithContentsOfURL:url];
        // 将二进制数据转为图片
        UIImage *image = [UIImage imageWithData:data];
        NSLog(@"come in %@", [NSThread currentThread]);
        // 回到主队列刷新ui
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            NSLog(@"come in %@", [NSThread currentThread]);
        });
    });
}

2. GCD技术浅谈

2.1 dispatch_barrier_async(栅栏函数)

栅栏函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。
注意: dispatch_barrier_async只能在自己创建的队列中才能生效。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self dispatchBarrier];
}

- (void)dispatchBarrier {
    // 自己创建的并行队列
    dispatch_queue_t queue = dispatch_queue_create("com.barrier", DISPATCH_QUEUE_CONCURRENT);
    
    // 在队列中添加任务
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"block1");
    });
    dispatch_async(queue, ^{
        NSLog(@"block2");
    });
    
    // 使用栅栏函数阻塞任务
    dispatch_barrier_async(queue, ^{
        NSLog(@"barrier block");
    });

    dispatch_async(queue, ^{
        NSLog(@"block3");
    });
    dispatch_async(queue, ^{
        NSLog(@"block4");
    });
}
栅栏函数输出结果

在上篇文章中我们提到过,多线程开发是存在安全风险的。在访问数据库或者文件的时候,如果是多个并发线程,会出现数据竞争问题。我们可以使用Serial Dispatch Queue来避免数据竞争问题,也可以采用内置的synchronization block,使用锁来实现某种同步机制。但是这两种机制的效率并不高。而根据dispatch_barrier_async的特性,我们可以使用barrier函数来处理数据的读写问题。下图为SDWebImage使用barrier函数来进行数据处理。


SDWebImageDownloader代码片段

2.2 dispatch_group(队列组)

在我们的工作中,经常会有这样的需求出现:分别执行两个异步的耗时操作,等到两个耗时操作都执行完毕后,回到主线程执行操作。
我们可以使用队列组(dispatch_group_t)快速,高效的实现上述需求。
以下是使用队列组相关的一些函数:

//创建队列组
dispatch_group_create()
//启动队列中的任务关联到队列组中
dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
//等待队列组关联的任务执行完毕,第二个参数为超时时间
dispatch_group_wait(<#dispatch_group_t group#>, <#dispatch_time_t timeout#>)
//为队列组设置通知一个任务,当队列组关联的任务执行完毕后,就调用这个任务。类似dispatch_barrier_async
dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
//手动管理队列组关联的任务的运行状态(或计数),进入和退出队列组次数必须匹配
dispatch_group_enter(<#dispatch_group_t group#>)
dispatch_group_leave(<#dispatch_group_t group#>)

在队列组的使用中,我们可以加入同一个队列的不同任务,也可以加入不同队列的任务。

/**
 *  使用wait函数demo
 */
- (void)waitDemo {
    //创建队列
    dispatch_queue_t queue1 = dispatch_queue_create("com.queue1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("com.queue2", DISPATCH_QUEUE_CONCURRENT);
    //创建队列组
    dispatch_group_t group = dispatch_group_create();
    //异步函数执行队列1的任务
    dispatch_async(queue1, ^{
        NSLog(@"task1 --- %@", [NSThread currentThread]);
    });
    //异步函数执行队列2的任务
    dispatch_async(queue2, ^{
        NSLog(@"task2 --- %@", [NSThread currentThread]);
    });
    //将队列1加入队列组
    dispatch_group_async(group, queue1, ^{
        //挂起队列1
        dispatch_suspend(queue1);
        NSLog(@"task1 finished! --- %@", [NSThread currentThread]);
    });
    //将队列2加入队列组
    dispatch_group_async(group, queue2, ^{
        //挂起队列2
        dispatch_suspend(queue2);
        NSLog(@"task2 finished! --- %@", [NSThread currentThread]);
    });
    //等待队列组任务执行
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    //异步函数执行队列3的任务
    dispatch_async(queue1, ^{
        NSLog(@"task3 --- %@", [NSThread currentThread]);
    });
    //异步函数执行队列4的任务
    dispatch_async(queue2, ^{
        NSLog(@"task4 --- %@", [NSThread currentThread]);
    });
    //恢复队列1
    dispatch_resume(queue1);
    //恢复队列2
    dispatch_resume(queue2);
}

从上面的例子中,我们可以看到。自己创建的队列加入到队列组中,等到队列组的任务执行结束后,再执行后面的队列中的其他任务。在这里用到了dispatch_suspenddispatch_resume函数。dispatch_suspend用来挂起当前队列,dispatch_resume用来恢复当前队列。这里注意一点,挂起一个队列并不会将当前正在执行的任务挂起。
如果我们现在有一个需求,下载两个图片,当两个图片都下载完成后,合成图片。我们有以下两种实现方式:

/**
 *  队列组wait等待下载结束
 */
- (void)dispatchGroupWaitDownloadPic {
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, kGetGlobalQueue, ^{
        NSLog(@"downloading in queue1 begin");
        // 下载图片1
        self.image1 = [self downloadImageWithUrlStr:@"http://attimg.dospy.com/img/day_141110/20141110_7ff16034289d3aac30c3ttvv7R1qtIua.jpg"];
        NSLog(@"downloading in queue1 end");
    });
    dispatch_group_async(group, kGetGlobalQueue, ^{
        NSLog(@"downloading in queue2 begin");
        // 下载图片2
        self.image2 = [self downloadImageWithUrlStr:@"http://attimg.dospy.com/img/day_141110/20141110_5a3cf0b3be54176f6975Rct5srdjy22J.jpg"];
        NSLog(@"downloading in queue2 end");
    });
    
    // 等待队列组关联的任务执行完毕
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_async(kGetGlobalQueue, ^{
        // 合成图片
        UIImage *image = [self synthesisPicWithImage:self.image1 image:self.image2];
        // 刷新UI
        dispatch_async(KGetMainQueue, ^{
            self.imageView1.image = self.image1;
            self.imageView2.image = self.image2;
            self.imageView3.image = image;
        });
    });
}

/**
 *  队列组notify等待下载结束
 */
- (void)dispatchGroupNotifyDownloadPic {
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, kGetGlobalQueue, ^{
        NSLog(@"downloading in queue1 begin");
        // 下载图片1
        self.image1 = [self downloadImageWithUrlStr:@"http://attimg.dospy.com/img/day_141110/20141110_7ff16034289d3aac30c3ttvv7R1qtIua.jpg"];
        NSLog(@"downloading in queue1 end");
    });
    dispatch_group_async(group, kGetGlobalQueue, ^{
        NSLog(@"downloading in queue2 begin");
        // 下载图片2
        self.image2 = [self downloadImageWithUrlStr:@"http://attimg.dospy.com/img/day_141110/20141110_5a3cf0b3be54176f6975Rct5srdjy22J.jpg"];
        NSLog(@"downloading in queue2 end");
    });
    
    // 等待队列组关联的任务执行完毕
    dispatch_group_notify(group, kGetGlobalQueue, ^{
        // 合成图片
        UIImage *image = [self synthesisPicWithImage:self.image1 image:self.image2];
        // 刷新UI
        dispatch_async(KGetMainQueue, ^{
            self.imageView1.image = self.image1;
            self.imageView2.image = self.image2;
            self.imageView3.image = image;
        });
    });
}

/**
 *  下载图片
 *
 *  @param urlStr 图片地址
 *
 *  @return 图片
 */
- (UIImage *)downloadImageWithUrlStr:(NSString *)urlStr {
    // 获取url
    NSURL *url = [NSURL URLWithString:urlStr];
    // 下载二进制数据
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 将二进制转为图片
    UIImage *image = [UIImage imageWithData:data];
    return image;
}

/**
 *  合成图片
 *
 *  @param image1 图片1
 *  @param image2 图片2
 *
 *  @return 合成后图片
 */
- (UIImage *)synthesisPicWithImage:(UIImage *)image1 image:(UIImage *)image2 {
    // 合并图片
    UIGraphicsBeginImageContext(CGSizeMake(200, 100));
    [image1 drawInRect:CGRectMake(0, 0, 100, 100)];
    [image2 drawInRect:CGRectMake(100, 0, 100, 100)];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    // 关闭上下文
    UIGraphicsEndImageContext();
    return image;
}

在上面的demo中,我们要注意dispatch_group_waitdispatch_group_notify的区别:

  • dispatch_group_wait: 函数会阻塞线程,所以在使用的时候,我们要用dispatch_async将整个方法放入后台来避免阻塞主线程。dispatch_group_wait的第二个参数我们这里设置的是DISPATCH_TIME_FOREVER(永远等待),我们也可以设定某一个dispatch_time_t类型的等待时间。
  • dispatch_group_notify: 以异步的方式工作。当队列组中没有任何任务时,它就会执行任务。

我们还可以使用dispatch_group_enterdispatch_group_leave添加任务到队列组中。注意:这两个函数要配对出现。

/**
 *  队列组EnterAndLeave等待下载结束
 */
- (void)dispatchGroupEnterAndLeaveDownloadPic {
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    //加任务到队列组
    dispatch_group_enter(group);
    dispatch_async(kGetGlobalQueue, ^{
        NSLog(@"downloading in queue1 begin");
        // 下载图片1
        self.image1 = [self downloadImageWithUrlStr:@"http://attimg.dospy.com/img/day_141110/20141110_7ff16034289d3aac30c3ttvv7R1qtIua.jpg"];
        NSLog(@"downloading in queue1 end");
        //离开队列组
        dispatch_group_leave(group);
    });

    //加任务到队列组
    dispatch_group_enter(group);
    dispatch_async(kGetGlobalQueue, ^{
        NSLog(@"downloading in queue2 begin");
        // 下载图片2
        self.image2 = [self downloadImageWithUrlStr:@"http://attimg.dospy.com/img/day_141110/20141110_5a3cf0b3be54176f6975Rct5srdjy22J.jpg"];
        NSLog(@"downloading in queue2 end");
        //离开队列组
        dispatch_group_leave(group);
    });
    
    // 等待队列组关联的任务执行完毕
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_async(kGetGlobalQueue, ^{
        // 合成图片
        UIImage *image = [self synthesisPicWithImage:self.image1 image:self.image2];
        // 刷新UI
        dispatch_async(KGetMainQueue, ^{
            self.imageView1.image = self.image1;
            self.imageView2.image = self.image2;
            self.imageView3.image = image;
        });
    });
}

2.3 dispatch_semaphore(信号量)

信号量是GCD用来同步的一种方式,当我们在处理一系列线程的时候,当数量达到一定量,通过信号量来快速控制并发,使用信号量包括三个函数:

//创建信号量 参数表示信号量的值
dispatch_semaphore_create(<#long value#>)
//发送一个信号 信号量值+1
dispatch_semaphore_signal(<#dispatch_semaphore_t dsema#>)
//等待信号 信号量值-1 第二个参数表示此函数阻塞当前线程等待的时间
dispatch_semaphore_wait(<#dispatch_semaphore_t dsema#>, <#dispatch_time_t timeout#>)

上面第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。

关于信号量,我看到了一个很有趣的例子用停车来比喻,希望可以帮助大家理解信号量的作用。
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就想把车停在这,所以就一直等下去。

/**
 *  信号量demo
 */
- (void)semaphoreDemo {
    int step = 3;
    int mainData = 0;
    //创建信号量
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_queue_create("com.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        int sum = 0;
        for (int i = 0; i < 5; i++) {
            sum += step;
            NSLog(@" >> Sum: %d", sum);
        }
        //发送信号
        dispatch_semaphore_signal(sem);
    });
    //等待信号
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    for (int i = 0; i < 5; i++) {
        mainData++;
        NSLog(@">> Main Data: %d",mainData);
    }
}
信号量demo输出结果

由上述例子我们可以看书,通过信号等待函数(dispatch_semaphore_wait)阻塞了主队列,等待StudyBlocks队列的任务执行完毕后,发送了信号才继续执行,实现线程同步。

2.4 dispatch_once(一次性代码)

使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
整个程序运行过程中,只会执行一次。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"点击了");
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"该行代码只执行一次");
    });
}

2.5 dispatch_after(延迟执行)

iOS常见的延时执行有2种方式
(1)调用NSObject的方法

// 2秒后再调用self的run方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];

(2)使用GCD函数

//第一个参数:when 过了多久执行的时间间隔
//第二个参数:queue 提交到的队列
//第三个参数:block 执行的任务
dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
//使用GCD函数延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后异步执行这里的代码...
});

代码示例:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touch screen");
    
    //NSObject延迟方法
    [self performSelector:@selector(run) withObject:self afterDelay:1];
    
    //延迟2秒后打印
    [self delayTime:2 block:^{
        NSLog(@"disatch after 3s");
    }];
    
}

/**
 *  封装GCD延迟执行函数
 *
 *  @param sceonds 延迟时间(多少秒)
 *  @param block   延迟执行的block
 */
- (void)delayTime:(int64_t)sceonds block:(dispatch_block_t)block {
    //GCD延迟执行函数
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(sceonds * NSEC_PER_SEC)), dispatch_get_main_queue(), block);
}

/**
 *  run方法
 */
-(void)run {
    NSLog(@"run");
}

2.6 dispatch_apply(迭代)

功能:把一项任务提交到队列中多次执行,具体是并行执行还是串行执行由队列本身决定.注意,dispatch_apply不会立刻返回,在执行完毕后才会返回,是同步的调用。

//第一个参数:执行的次数
//第二个参数:提交到的队列
//第三个参数:执行的任务
dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t queue#>, <#^(size_t)block#>)

当我们有一系列不相关的循环操作时,可以通过dispatch_apply函数把这些循环提交到后台线程并行执行。此时循环任务调度到后台,执行效率提高,能抵消队列调度本身的开销,显著提高效率。
示例代码:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //串行队列的dispatch_apply
    dispatch_queue_t serialQueue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
    dispatch_apply(10, serialQueue, ^(size_t index) {
        NSLog(@"apply -- %zu", index);
    });
    
    //并行队列的dispatch_apply
    dispatch_queue_t currentQueue = dispatch_queue_create("com.comcurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, currentQueue, ^(size_t index) {
        NSLog(@"apply -- %zu", index);
    });
}

/**
 *  apply造成死锁
 */
- (void)deadlock {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
    dispatch_apply(10, serialQueue, ^(size_t index) {
        NSLog(@"apply -- %zu", index);
        dispatch_apply(10, serialQueue, ^(size_t index) {
            NSLog(@"apply -- %zu", index);
        });
    });
}

这里要注意一下dispatch_apply的"坑",由于dispatch_apply是同步调用的,所以要注意deadlock方法中,当使用串行队列进行dispatch_apply的嵌套使用时,会造成死锁。


本文中的代码已经上传GitHub,希望本文章能对大家有所帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容