iOS 多线程之GCD

GCD 简介
1、什么是GCD?
全称是 Grand Central Dispatch,纯 C 语言编写,提供了非常多强大的函数
2、GCD的优势是什么?
GCD 是苹果公司为多核的并行运算提出的解决方案
GCD 会自动利用更多的CPU内核(比如双核、四核)
GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

串行并行、同步异步

一、概念

  • 同步执行(sync):在当前线程中执行任务,任务未执行完时,会阻塞线程,不会开辟线程

  • 异步执行(async):另开辟线程执行任务,不会阻塞当前线程

  • 串行队列:按顺序一个一个执行,FIFO先进先出原则

  • 并行队列:可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

一般使用中会有如下四种情况

  • 串行队列,同步执行:在当前线程中,一个个执行
  • 串行队列,异步执行:另开辟一个线程,一个个执行
  • 并行队列,同步执行:在当前线程中,一个个执行
  • 并行队列,异步执行:另开辟多个线程,同时执行

也就是说串行同步和并行同步作用是一样的。

二、实例效果

结合代码看一下效果
串行同步

/**
 串行队列,同步执行
 */
- (void)createSerialQueueWithSync {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"A==%@", [NSThread currentThread]);
    dispatch_sync(serialQueue, ^{
        NSLog(@"B==%@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"C==%@", [NSThread currentThread]);
    });
    NSLog(@"D==%@", [NSThread currentThread]);
}

串行同步打印结果如下:


串行同步运行结果

从结果中我们能看到,符合串行队列的特征(一个一个执行),符合同步执行特征(在当前线程执行,并且阻塞了当前线程)。

串行异步

/**
 串行队列,异步执行
 */
- (void)createSerialQueueWithAsync {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        NSLog(@"A==%@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"B==%@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"C==%@", [NSThread currentThread]);
    });
    NSLog(@"D==%@", [NSThread currentThread]);
}

串行异步打印结果如下


串行异步运行结果

从结果中可以看到,符合串行队列的特征(一个一个执行),符合异步执行特征(另开辟线程,且不阻塞当前线程)。

并行同步

/**
 并行队列,同步执行
 */
- (void)createConcurrentQueueWithSync {
    dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
    //    dispatch_queue_t conCurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//这个是全局并行队列,一般并行任务都会加到这里面去
    
    dispatch_sync(conCurrentQueue, ^{
        NSLog(@"A==%@", [NSThread currentThread]);
    });
    dispatch_sync(conCurrentQueue, ^{
        NSLog(@"B==%@", [NSThread currentThread]);
    });
    dispatch_sync(conCurrentQueue, ^{
        NSLog(@"C==%@", [NSThread currentThread]);
    });
    NSLog(@"D==%@", [NSThread currentThread]);
}

并行同步打印结果如下


并行同步运行结果

从结果可以看出,符合同步特征(在当前线程,且阻塞当前线程),但是并不清楚是否符合并行队列的特征,并行队列的效果需要异步执行才能看出来,并行同步整体的效果和串行同步相同。

并行异步

/**
 并行队列,异步执行
 */
- (void)createConcurrentQueueWithAsync {
    dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"A==%@", [NSThread currentThread]);
    });
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"B==%@", [NSThread currentThread]);
    });
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"C==%@", [NSThread currentThread]);
    });
    NSLog(@"D==%@", [NSThread currentThread]);
}

并行异步打印结果如下


并行异步运行结果

从结果中可以看出,符合并行队列特征(可以并发执行线程任务),符合异步执行特征(另开辟线程,且不阻塞当前线程)。

//全局并发队列
dispatch_get_global_queue
//主线程--串行队列
dispatch_get_main_queue()

这两个队列在系统刚启动时就创建好的队列,一般不建议使用全局并发队列,因为在全局并发队列中也会执行系统的任务,对于调试和业务剥离等造成影响。

死锁

死锁总结一句话:串行队列(当然也包括主队列)中向该队列添加同步任务,必定导致死锁。

两个死锁案例帮助理解这句话,
例 1:

- (void)test1 {
    //1,5,2,造成死锁,
    //1,5,2,async是异步,所以会开辟线程,当然开辟线程需要耗时,所以5在先,2在后
    //为什么会造成死锁?因为队列中加入任务的顺序是2、4、3,按照队列的FIFO原则,理应4执行完再执行3,但执行完2遇到了sync同步函数,需要阻塞线程,4需要等待3执行完再执行,造成了互相等待即死锁。
    NSLog(@"1==%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"2==%@", [NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"3==%@", [NSThread currentThread]);
        });
        NSLog(@"4==%@", [NSThread currentThread]);
    });
    NSLog(@"5==%@", [NSThread currentThread]);
}

输出结果是 1、5、2,然后发生死锁崩溃,执行过程如下

  1. 主队列按顺序先执行 test 代码块简称 A,由于dispatch_async(queue是异步串行的另一个队列,不阻塞当前队列,所以这里先执行了 A 的 1 和 5。
  2. 来到dispatch_async(queue代码块,另起了新的线程处理队列 queue,已知 queue 里面是(2、dispatch_sync(queue、4)代码块简称 B。
  3. 执行 B 的 2
  4. 执行dispatch_sync(queue时发现这是同步函数,需要阻塞队列 queue,在当前线程执行dispatch_sync(queue代码块,但这个代码块的要在 queue 中执行,而 queue 已经被阻塞了,造成了互相等待,导致死锁。

例 2:

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"deadlock");
    });
}

同步对于任务是立即执行的,当把任务放进主队列时,它会立即执行,只有执行完这个任务,viewDidLoad才会继续执行。
viewDidLoad和任务都在主队列上,根据队列的先入先出原则,需要先执行完viewDidLoad再执行任务,造成了互相等待,导致死锁。

栅栏函数barrier

一、概念
dispatch_barrier_async加入到自定义的并行队列(不能是全局global队列)时,程序会先执行队列中barrier之前的任务,再执行barrier的任务,最后再执行队列中barrier之后的任务。

二、实例
直接上代码
实例一、应用了dispatch_barrier_async的队列中,线程的执行顺序。

- (void)createBarrierAsync {
    dispatch_queue_t queue = dispatch_queue_create("liufeng", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"start==%@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        NSLog(@"A==%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"B==%@", [NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"barrier_async==%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"C==%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"D==%@", [NSThread currentThread]);
    });
    NSLog(@"end==%@", [NSThread currentThread]);
}

控制台打印效果如下


栅栏函数的打印结果

从打印结果我们可以看到,程序会先执行队列中barrier之前的任务,再执行barrier的任务,最后再执行队列中barrier之后的任务。

实例二、从网络加载图片,并给图片加上水印后显示到屏幕上

- (void)testPic {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.waterImage", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        NSString *logoStr = @"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3351002169,4211425181&fm=27&gp=0.jpg";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
    
    dispatch_async(concurrentQueue, ^{
        NSString *logoStr = @"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3033952616,135704646&fm=27&gp=0.jpg";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
    
    __block UIImage *newImage = nil;
    dispatch_barrier_async(concurrentQueue, ^{
        
        for (int i = 0; i<self.mArray.count; i++) {
            UIImage *waterImage = self.mArray[i];
            newImage = [LFPrintImageTool printText:@"小姑娘还不睡呀?" onImage:waterImage];
        }
    });
    
    
    dispatch_async(concurrentQueue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = newImage;
        });
    });
}

模拟器显示如图所示


栅栏函数应用在图片加水印

这个从结果看不出来什么,主要看代码,我们程序的步骤是这样的,先将图片从网络获取下来,才能给它加水印,最后显示到屏幕上,栅栏函数相当于阻塞了当前线程。

注意:栅栏函数必须是自定义的并发队列才有效,且必须是同一队列中的线程才有效。

调度组

一、概念
在调度组内的线程都执行完毕后,dispatch_notify函数会触发回调。

二、实例
应用场景:
一个业务需要开启N个异步线程,但是后续操作,需要依赖N个线程返回的数据完成操作。
直接上代码
实例一

- (void)createGroupQueue {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{
        NSLog(@"A---%@", [NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"B---%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"C---%@", [NSThread currentThread]);
    });
    
    dispatch_notify(group, queue, ^{
        NSLog(@"队列组任务执行完毕");
    });
}

调度组执行结果如下

调度组执行结果

从结果中可以看出,符合预期,调度组内的线程都执行完毕后,dispatch_notify函数会触发回调。

实例二

- (void)test2 {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"A---%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"B---%@", [NSThread currentThread]);
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"C---%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_notify(group, queue, ^{
        NSLog(@"队列组任务执行完毕");
    });
}

上面这个实例是以进组出组的方式实现调度。
dispatch_group_enter(group);
dispatch_group_leave(group);
进组出组的方式类似于信号量,内部有一个signal,enter加1,leave减1,它们总是成对出现,当signal为0时,表示调度组里面的任务都执行完了。

信号量

一、作用
可以控制并发队列的最大并发数,当创建的信号量限制为1时,可以达到锁的效果。

二、概念
信号量,一般用来线程并发数量,信号量为几,线程最大并发数就是几
//创建信号量,参数:信号量的初值,当信号量小于0时阻塞当前线程
dispatch_semaphore_create(信号量值)

//等待降低信号量
dispatch_semaphore_wait(信号量,等待时间)

//提高信号量
dispatch_semaphore_signal(信号量)

三、实例

- (void)createSemaphoreQueue {
    //create的value表示,最多几个资源可访问
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //任务1
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);
    });
    //任务2
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);
    });
    //任务3
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);
    });
}

控制台打印效果如下


信号量测试.png

再次执行效果如下


信号量测试

可以看到,始终都是task1和task2先执行完,信号量经过dispatch_semaphore_signal增加以后,task3才能执行,结论就是不论并发队列中有多少任务等待执行,同一时间只允许两个任务执行。

总结

以上就是GCD的基本使用,在很多大型的底层框架中,很多使用的都是NSOperation,因为NSOperation更加灵活,开发者可以更好的监控线程的生命周期以及做处理。

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

推荐阅读更多精彩内容

  • 多线程 在iOS开发中为提高程序的运行效率会将比较耗时的操作放在子线程中执行,iOS系统进程默认启动一个主线程,用...
    郭豪豪阅读 2,608评论 0 4
  • GCD (Grand Central Dispatch) :iOS4 开始引入,使用更加方便,程序员只需要将任务添...
    池鹏程阅读 1,344评论 0 2
  • 继上一篇说到的NSTread之后 本文主要分享下GCD的一些总结主要是按一下几点进行: GCD是什么? GCD任务...
    土鳖不土阅读 1,873评论 2 8
  • 1. GCD简介 什么是GCD呢?我们先来看看百度百科的解释简单了解下概念 引自百度百科:Grand Centra...
    千寻_544f阅读 393评论 0 0
  • 每个人总有自己不想触碰的地方,但总会有一些人能让你不受控制的走出自己的舒适区。不管初衷是否真诚,我与酒儿在一起的时...
    自此不饮酒阅读 600评论 12 7