学习GCD看我就够了

什么是多线程?
计算机在运行一段程序的时候,会把该程序的CPU命令列配置到内存中,然后按照顺序一个一个执行命令列,这样1个CPU执行的CPU命令列为一条无分叉路径就是线程。
而有多条这样的执行指令列的路径存在时即为多线程。
iOS实现多线程有4种方法

  • pthreads
  • NSThread
  • GCD
  • NSOperation & NSOperationQueuef

这里我们主要讲GCD

一、Dispatch Queue和线程的关系

什么是Dispatch Queue?
如其名称,是执行处理的等待队列。当我们通过dispatch_async等函数把Block加入Dispatch Queue后,Dispatch Queue按照追加的顺序(FIFO)执行处理。

通过Dispatch Queue执行处理

Dispatch Queue的种类

  • Serial Dispatch Queue(串行队列) ——等待现在执行中处理结束再加入队列
  • Concurrent Dispatch Queue(并发队列) ——不等待现在执行中处理结束,直接加入队列
Serial Dispatch Queue
Concurrent Dispatch Queue

用代码说明:

Serial Dispatch Queue

    dispatch_queue_t serial_queue = dispatch_queue_create("come.tanpei", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serial_queue, ^{
        NSLog(@"block 1");
    });
    dispatch_async(serial_queue, ^{
        NSLog(@"block 2");
    });
    dispatch_async(serial_queue, ^{
        NSLog(@"block 3");
    });
    dispatch_async(serial_queue, ^{
        NSLog(@"block 4");
    });

输出如下:

2017-09-27 11:43:40.230126+0800 aegewgr[4327:1296458] block 1
2017-09-27 11:43:40.230335+0800 aegewgr[4327:1296458] block 2
2017-09-27 11:43:40.230461+0800 aegewgr[4327:1296458] block 3
2017-09-27 11:43:40.230548+0800 aegewgr[4327:1296458] block 4

这里Serial Dispatch Queue只会使用一个线程,因为它是串行队列,只会当一个处理执行完了才会将下一个任务交给线程处理。

Concurrent Dispatch Queue

    dispatch_queue_t concurrent_queue = dispatch_queue_create("come.tanpei", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrent_queue, ^{
        NSLog(@"block 1");
    });
    dispatch_async(concurrent_queue, ^{
        NSLog(@"block 2");
    });
    dispatch_async(concurrent_queue, ^{
        NSLog(@"block 3");
    });
    dispatch_async(concurrent_queue, ^{
        NSLog(@"block 4");
    });

输出如下:

2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304484] block 3
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304483] block 1
2017-09-27 11:45:09.057522+0800 aegewgr[4349:1304486] block 4
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304485] block 2

block的执行完成
是随机的,因为他们虽然是按顺序把任务提交给线程,但是因为不需要等待前一个任务执行,所以几乎是同时交给线程处理的。所以这里会使用多个线程,而具体线程数的多少由XNU内核决定。

Concurrent Dispatch Queue的执行

二、Dispatch Queue的使用

1、获取队列

在使用Dispatch Queue的时候我们可以通过dispatch_queue_create函数创建队列,也可以获取系统给我们提供的队列。系统给我们提供了两种队列

系统提供的Dispatch Queue
2、同步与异步
  • dispatch_async表示异步:将指定的Block”非同步“加入Dispatch Queue,不做任何等待
异步执行
  • dispatch_sync表示同步:将指定的Block”同步“的加入Dispatch Queue,在Block结束之前,dispatch_sync函数会一直等待
同步执行
3、死锁

由于dispatch_sync会等待Block执行结束才会继续往下执行,所以会产生死锁的情况

我们直接在主线程中同步加入一个Blcok:

dispatch_queue_t main_queue = dispatch_get_main_queue();
    dispatch_sync(main_queue, ^{
        NSLog(@"main queue");
    });
    NSLog(@"go on");

无任何输出,程序直接卡死了。这就是造成了死锁
因为该源代码在main_queue(主线程)中加入一个加入一个指定的Block,并等待其执行结束。而由于main_queue是一个串行队列,它要等当前线程中的任务处理完后才会把队列中的任务提交到主线程,而主线程又在等待这段代码执行,所以造成了相互等待,就产生了死锁。(而并发队列不会产生死锁)
如:

dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
    dispatch_sync(global_queue, ^{
        NSLog(@"global_queue out");
        dispatch_sync(global_queue, ^{
            NSLog(@"global_queue in");
        });
    });

输出如下:

2017-09-27 16:11:56.332317+0800 aegewgr[4723:1590202] global_queue out
2017-09-27 16:11:56.332446+0800 aegewgr[4723:1590202] global_queue in

所以产生死锁的话一般都是在串行队列中并且是在一个线程中同步往这个线程提交一个Block。

4、Dispatch Group(派发分组)

Dispatch Group是GCD的一项特性,能够把任务分组。调用者在这组任务执行完毕后会得到通知,并做相应的处理。

创建:dispatch_group_t group = dispatch_group_create();
同样的,它也有
dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, <#^(void)block#>)dispatch_sync函数没有什么区别,它只是多了一个dispatch_group_t参数,来把任务进行分组。
还有一种方法能把任务加入dispatch_group,那就是下面这对情侣:

    dispatch_group_enter(dispatch_group_t group);
    dispatch_group_leave(dispatch_group_t group);

记住,这对情侣一定要成对出现,dispatch_group_enter就是标志下面的代码要加入dispatch_group。dispatch_group_leave就是表示加入dispatch_group的代码结束。也就是说dispatch_group_enter和dispatch_group_leave之间的代码就是加入dispatch_group中的。

说了这么多,把一个队列加入dispatch_group后有什么用呢?主要就是一组相似的操作结束后,你可以通过dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue, dispatch_block_t notification_block)函数来获得通知,并进行相应的处理。Block参数就是你要添加的处理。

当然,如果你想设置一个等待时间,可以使用dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)函数,该函数设置了一个等待时间也就是说程序要一直阻塞当前线程直到group中的任务执行完毕或者超过等待时间,才会继续往下执行。
dispatch_block_notify函数不会阻塞当前线程,它只是指定了一个group任务执行完后的回调。

需要举个栗子吗?
好吧,还是举个栗子吧。

dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
    dispatch_group_t group = dispatch_group_create();
    ;
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 1");
    });
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 2");
    });
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 3");
    });
    dispatch_group_notify(group, global_queue, ^{
        NSLog(@"notify");
    });
    NSLog(@"other task");

输出如下:

2017-09-27 17:06:37.795564+0800 aegewgr[4983:1713915] other task
2017-09-27 17:06:37.795571+0800 aegewgr[4983:1714182] task 3
2017-09-27 17:06:37.795578+0800 aegewgr[4983:1714181] task 1
2017-09-27 17:06:37.795578+0800 aegewgr[4983:1714183] task 2
2017-09-27 17:06:37.795813+0800 aegewgr[4983:1714183] notify

可以看到dispatch_group_notify并没有阻塞当前线程,而且它提交的Block一定是当group中的所有任务执行完后才会执行。另外,这里的queue可以不是一个queue,你可以使用任意其它queue,不过最好是并发队列,如果是串行队列,任务会按顺序一个一个执行,那使用group的意义就不大了。

看看dispatch_group_wait

dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
    dispatch_group_t group = dispatch_group_create();
    ;
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 1");
    });
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 2");
    });
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 3");
    });
    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1));
    NSLog(@"other task");

输出如下:

2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726567] task 2
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726568] task 1
2017-09-27 17:10:10.968140+0800 aegewgr[5002:1726569] task 3
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726443] other task

可以看到dispatch_group_wait函数阻塞了当前线程,只有当group中的所有任务执行完后线程才会继续往下执行。

5 、其它相关函数
  • dispatch_barrier_async和dispatch_barrier_sync(栅栏)

这两个函数的作用差不多,都是把它前面和它后面的函数分隔开。使它前面的任务先执行,再执行它添加的任务,最后执行它后面的任务。

那么它们有什么区别呢?
当然从名字就能看出来,就是提交任务的方式不同,一个是同步一个是异步,同步和异步的区别前面有解释,如果忘了的话,可以再回去看看。

  • Dispatch Semaphore(信号量)

信号量其实就是用来保证访问资源的线程数,当信号量大于等于1时,资源可以访问,否则无法访问资源,直到其它线程释放资源。

这里主要有三个函数:

dispatch_semaphore_t dispatch_semaphore_create(long value);  //创建一个dispatch_semaphore_t,value为初始信号量
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);   //信号量-1
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);   //信号量+1

怎么用呢?
还是举个栗子吧:
假如有两个资源,但是同时有三个线程想要访问,就可以使用信号量进行控制:

//crate的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);
    });

输出如下:

2017-09-27 18:04:27.590428+0800 aegewgr[5149:1860224] run task 1
2017-09-27 18:04:27.590428+0800 aegewgr[5149:1860221] run task 2
2017-09-27 18:04:28.591086+0800 aegewgr[5149:1860224] complete task 1
2017-09-27 18:04:28.591086+0800 aegewgr[5149:1860221] complete task 2
2017-09-27 18:04:28.591386+0800 aegewgr[5149:1860219] run task 3
2017-09-27 18:04:29.591845+0800 aegewgr[5149:1860219] complete task 3

假如把信号量设置为3呢?

 dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);

输出如下:

2017-09-27 18:08:37.535634+0800 aegewgr[5169:1873722] run task 2
2017-09-27 18:08:37.535637+0800 aegewgr[5169:1873721] run task 1
2017-09-27 18:08:37.535636+0800 aegewgr[5169:1873723] run task 3
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873723] complete task 3
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873721] complete task 1
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873722] complete task 2
  • dispatch_once
    此函数在我们创建单例的时候经常会用到,就是可以保证在应用程序执行中该函数只执行一次。即使在多线程环境也,也可以保证百分百的安全。
写在文末

本来是打算写一篇关于多线程的文章的,因为想把GCD介绍的全面一点,所以篇幅就有点长了,另外三种方式请看这里

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

推荐阅读更多精彩内容

  • GCD (Grand Central Dispatch) :iOS4 开始引入,使用更加方便,程序员只需要将任务添...
    池鹏程阅读 1,323评论 0 2
  • 简介 GCD(Grand Central Dispatch)是在macOS10.6提出来的,后来在iOS4.0被引...
    sunmumu1222阅读 850评论 0 2
  • iOS中GCD的使用小结 作者dullgrass 2015.11.20 09:41*字数 4996阅读 20199...
    DanDanC阅读 810评论 0 0
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,016评论 0 4
  • 我曾见到 那绛紫色的少年 渴望万丈光芒 却痛恶着太阳的灼伤 追光的人儿 那恐惧啊 欲盖弥彰……
    青时_阅读 63评论 0 3