iOS多线程详细说明(GCD篇-就是个玩儿)

相信对于一些开发者,这个词并不陌生,而且面试中经常会被问到。开发过程中我们也会经常用到,单例、延时、遍历、异步操作...今天就来细说一下GCD是啥,是用来干啥的

GCD全称为Grand Central Dispatch,GCD是一套低层级的C API,通过 GCD,开发者只需要向队列中添加一段代码块(block或C函数指针),而不需要直接去操作线程。

进入正题之前,我们先来了解几个概念

  • 1.进程: 一个具有一定独立功能的程序关于某个数据集合的一次 运行活动。可以理解成一个运行中的应用程序(比如我的妈妈在做饭)自己可以呼出任务管理器,里面 都是进程。
  • 2.线程: 程序执行流的最小单元,线程是进程中的一个实体。通俗的理解就是 [妈妈在做饭,其中手在切菜(一个线程),耳朵在听音乐(另外一个线程)]
  • 3.同步: 只能在当前线程按先后顺序依次执行,不开启新线程 [妈妈做饭:只能锅里先倒油(线程1),才能炒菜(线程2),不倒油是炒不了菜的]。
  • 4.异步: 可以在当前线程开启多个新线程执行,可不按顺序执行
    [妈妈在炒菜的时候(线程1),米饭是在煮的(线程2)]。
  • 5.队列: 装载线程任务的队形结构。
  • 6.并发: 线程执行可以同时一起进行执行(对应异步)。
  • 7.串行: 线程执行只能依次逐一先后有序的执行(对应同步)。

GCD有几种Queue(队列)

  • 1.****The main queue****(主线程串行队列): 与主线程功能相同,提交至Main queue的任务会在主线程中执行, Main queue 可以通过dispatch_get_main_queue()来获取。

  • 2.****Global queue****(全局并发队列): 全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别。 Global queue 可以通过调用dispatch_get_global_queue函数来获取(可以设置优先级)

  • 3.****Custom queue**** (自定义队列): 可以为串行,也可以为并发。Custom queue 可以通过dispatch_queue_create()来获取;

  • 4.****Group queue ****(队列组):将多线程进行分组,最大的好处是可获知所有线程的完成情况。Group queue 可以通过调用dispatch_group_create()来获取,通过dispatch_group_notify,可以直接监听组里所有线程完成情况。

关于线程

dispatch_asyn和dispatch_sync添加任务到dispatch队列时,是否创建线程呢,那么创建线程是创建一个呢还是多个呢?

  • 1.dispatch_sync添加任务到队列,不会创建新的线程都是在当前线程中处理的。无论添加到串行队列里或者并行队列里,都是串行效果,因为这个方法是等任务执行完成以后才会返回。
  • 2.dispatch_async添加任务到
  • 2.1:mainQueue不创建线程,在主线程中串行执行
  • 2.2:globalQueue 和 并行队列:根据任务系统决定开辟线程个数
  • 2.3:串行对列:创建一个线程:串行执行。

贴代码的时间段了

1.异步 主线程(一般用做刷新UI操作)

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"123");
    //异步 主线程 串行队列  这个相当于放在主线程的最后面去执行的 (FIFO)
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        NSLog(@"我是获取主队列执行的任务1 %@",[NSThread currentThread]);
    });
    dispatch_async(mainQueue, ^{
        NSLog(@"我是获取主队列执行的任务2 %@",[NSThread currentThread]);
    });
    dispatch_async(mainQueue, ^{
        NSLog(@"我是获取主队列执行的任务3 %@",[NSThread currentThread]);
    });
    NSLog(@"主线程的结尾 %@",[NSThread currentThread]);
    sleep(2);//?< 卡住主线程2秒
}

打印结果:
123
主线程的结尾** <NSThread: 0x60800007d780>{number = 1, name = main}**
我是获取主队列执行的任务1 <NSThread: 0x60800007d780>{number = 1, name = main}
我是获取主队列执行的任务2 <NSThread: 0x60800007d780>{number = 1, name = main}
我是获取主队列执行的任务3 <NSThread: 0x60800007d780>{number = 1, name = main}

//关键字:**dispatch_async**---异步执行,也就是说block里面代码的执行,跟当前didload 里面的没关系 所有当碰到直接略过,由于是**dispatch_get_main_queue()** 所以当主线程结束的时候 3个任务才执行。(**主线程一般用来刷新UI操作,这个是由顺序的哟,为什么会有顺序呢,因为这些任务所在的队列是串行队列,在添加任务时候,是按照代码的顺序来的,所以在执行的时候要保证FIFO(先进先出)的原则来执行**)

###2.同步 主线程(死锁---禁忌)

//同步 主线程 串行 死锁

  • (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"我是死锁的上面");
    dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"我是死锁 线程:%@",[NSThread currentThread]);
    });
    NSLog(@"我是死锁的下面");
    }
>打印结果:
我是死锁的上面
***为什么会出现这种情况呢 我给大家画一个图 就很好明白了***

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/4241428-8af55b01905d6087.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/200)

>**dispatch_get_main_queue()** 刚才已经介绍过了 ,是将任务放在最底下,也就是当主线程的该走的都走完了,没啥事了 才处理你的任务,**dispatch_sync** (同步),也就是说要等block里的任务执行完才能继续往下走,要不然就卡住
**看一下上图,整个程序要等block里面的执行完才能执行,而block的执行需要整个程序走完才能执行,两个东西互相都在等,那么永远都不能见面**

###2.异步 其它线程(一般的耗时操作:I/O操作,上传照片...)
  • (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"123");
    //异步 子线程 并行队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
    NSLog(@"异步1 线程 :%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    NSLog(@"异步2 线程 :%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    sleep(2);
    NSLog(@"异步3 线程 :%@",[NSThread currentThread]);
    });
    NSLog(@"456");
    NSLog(@"主线程即将结束 %@",[NSThread currentThread]);
    }

>```
打印结果:
123
456
异步1 线程:<NSThread: 0x608000270740>{number = 6, name = (null)}
异步2 线程:<NSThread: 0x60000026e540>{number = 7, name = (null)}
主线程即将结束<NSThread: 0x60000007da00>{number = 1, name = main}
异步3 线程 :<NSThread: 0x608000270100>{number = 8, name = (null)}

这里面我让任务3卡当前线程2秒,我们可以看到,主线程依然往下走了,说明这个3个任务的执行完全跟主线程没有关系

Global Queue 的优先级

#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
dispatch_queue_t globaleQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globaleQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globaleQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globaleQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

dispatch_async(globaleQueue1, ^{
   NSLog(@"我是任务1 %@",[NSThread currentThread]);
});
dispatch_async(globaleQueue3, ^{
    NSLog(@"我是任务3 %@",[NSThread currentThread]);
});

dispatch_async(globaleQueue4, ^{
    NSLog(@"我是任务4 %@",[NSThread currentThread]);
});

dispatch_async(globaleQueue2, ^{
    NSLog(@"我是任务2 %@",[NSThread currentThread]);
});

我是任务1 <NSThread: 0x600000274dc0>{number = 6, name = (null)}
我是任务2 <NSThread: 0x600000274440>{number = 8, name = (null)}
我是任务3 <NSThread: 0x600000274080>{number = 7, name = (null)}
我是任务4 <NSThread: 0x60800026d480>{number = 9, name = (null)}

PS:异步发送的,但是无论我们运行多少次,看到的结果都是有顺序的,也就验证了优先级的事情

前3个我就不做过多的介绍了,看字面意思就大概清楚了,哪个queue的优先级最高,当然是high喽!主要介绍的就是这个DISPATCH_QUEUE_PRIORITY_BACKGROUND,我们先来看它后面那个是什么(INT16_MIN)

INT16_MIN

我们可以看到,苹果的源码里我们看的很清楚前 3 个优先级的级别分别是2、0、-2,也就是说2是最高的, -2是最低的. INT16_MIN是个什么鬼!?字面上理解,16位最小值( Minimum value of a signed 16-bit integer)-2^15(- 32768 ),至于为什么是- 32768 ,这个涉及到一点计算机基础(补码),自行查阅。
说了一堆废话 也就是说DISPATCH_QUEUE_PRIORITY_BACKGROUND优先级最低,那么有人就会问,我们什么时候会用到这个DISPATCH_QUEUE_PRIORITY_BACKGROUND呢?还要补充一点知识 Throttle 机制,这里不做过多介绍(因为我不知道,嘻嘻!)

DISPATCH_QUEUE_PRIORITY_BACKGROUND应用场景

对于重度磁盘I/O依赖的后台任务,如果对实时性要求不高,放到DISPATCH_QUEUE_PRIORITY_BACKGROUND Queue中是个好习惯,对系统更友好(别人的话)
也就是说,一些非常耗时的操作,但是啥时候执行完都成,那就放在最后喽,反正我不要立刻看到结果,这个时候就用DISPATCH_QUEUE_PRIORITY_BACKGROUND

3.Custom Queue

//子线程  串行队列
//DISPATCH_QUEUE_SERIAL              串行
//DISPATCH_QUEUE_CONCURRENT          并行
//DISPATCH_QUEUE_SERIAL_INACTIVE     串行不活跃的
//DISPATCH_QUEUE_CONCURRENT_INACTIVE 并行不活跃的

dispatch_queue_t customQueue = dispatch_queue_create("key", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(customQueue, ^{
     //任务
     NSLog(@"我是自定义任务 %@",[NSThread currentThread]);
});

这个东西如果要用的话,用前两个就足够了

4.Dispatch_Group(监听任务完成情况)

dispatch_group_t 主要跟 dispatch_group_notify来配合使用,当放在 dispatch_group_t 里面的队列和任务都完成以后,会响应dispatch_group_notify

dispatch_group_t group = dispatch_group_create(); //?< 创建一个组
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //?< 创建一个全局队列

dispatch_group_async(group, globalQueue, ^{
    sleep(1);
    NSLog(@"任务1  %@",[NSThread currentThread]);
});
dispatch_group_async(group, globalQueue, ^{
    sleep(1);
    NSLog(@"任务2  %@",[NSThread currentThread]);
});
dispatch_group_async(group, globalQueue, ^{
    sleep(1);
    NSLog(@"任务3  %@",[NSThread currentThread]);
});
dispatch_group_async(group, globalQueue, ^{
    sleep(1);
    NSLog(@"任务4  %@",[NSThread currentThread]);
});
//这一定要放在添加队列的后面
dispatch_group_notify(group, globalQueue, ^{
    NSLog(@"结束了 %@",[NSThread currentThread]);
});

打印结果:
任务1 <NSThread: 0x608000270000>{number = 6, name = (null)}
任务2 <NSThread: 0x600000275880>{number = 5, name = (null)}
任务3 <NSThread: 0x60800026fc40>{number = 4, name = (null)}
任务4 <NSThread: 0x60800026e540>{number = 3, name = (null)}
结束了 <NSThread: 0x608000270000>{number = 6, name = (null)}

5.Dispatch Semaphore(信号量)

dispatch_semaphore_create; //创建一个信号量
dispatch_semaphore_wait   让那个信号量-1
dispatch_semaphore_signal 让那个信号量+1

先说说信号量的工作原理,当我初始化一个信号量的时候,会给他一个初始值 **dispatch_semaphore_create(1) **,然后我们调用 dispatch_semaphore_wait(上面的信号量对象, DISPATCH_TIME_FOREVER) 会检测那个信号量的值是否大于 0 ,如果大于0就继续执行下面的代码,并把那个信号量的值-1,如果 小于等于0 那么当前线程就会卡到这一句,等待信号量的值增加并且大于 0
dispatch_semaphore_signal 就是用来增加信号量的值的,下面我们看一段代码

    //1.创建信号量 并且设置为10
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    //2.创建个queue
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    for (int i = 0; i < 100; i++)
    {   // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10此后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
            NSLog(@"%i",i);
            sleep(2);
            // 每次发送信号则semaphore会+1,
            dispatch_semaphore_signal(semaphore);
        });
    }

打印结果:
每两秒钟打印10次,就不贴了

6.Dispatch Barrier(类似信号量的东西[Barrier:障碍物])

dispatch_barrier_async 这个东西用作依赖关系,举个例子: (1,2,3任务)执行完 -->barrier任务 -->(4,5,6任务)

dispatch_queue_t barrierQueue = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);//?< 自定义以个并发队列

dispatch_async(barrierQueue, ^{
    NSLog(@"任务1");
});
dispatch_async(barrierQueue, ^{
    NSLog(@"任务2");
});
dispatch_async(barrierQueue, ^{
    NSLog(@"任务3");
});

// 会等待 队列里面之前的任务都完成以后在执行下面的
dispatch_barrier_async(barrierQueue, ^{
    sleep(5);
    NSLog(@"barrier任务");
});

NSLog(@"mark-----1");

dispatch_async(barrierQueue, ^{
    NSLog(@"任务4");
});

NSLog(@"mark-----2");

dispatch_async(barrierQueue, ^{
    NSLog(@"任务5");
});
dispatch_async(barrierQueue, ^{
    NSLog(@"任务6");
});

打印结果:
2017-01-16 13:49:22.138 xxx[1690:159260] mark-----1
2017-01-16 13:49:22.138 xxx[1690:219423] 任务1
2017-01-16 13:49:22.138 xxx[1690:219424] 任务3
2017-01-16 13:49:22.138 xxx[1690:219414] 任务2
2017-01-16 13:49:22.139 xxx[1690:159260] mark-----2
(这之间延迟5秒)
2017-01-16 13:49:27.141 xxx[1690:219414] barrier任务
2017-01-16 13:49:27.142 xxx[1690:219414] 任务4
2017-01-16 13:49:27.142 xxx[1690:219424] 任务5
2017-01-16 13:49:27.142 xxx[1690:219423] 任务6

>我们很清楚的可以看到 barrier任务是在 任务1、2、3完成以后执行的,也就是在barrier任务执行的时候 4、5、6的任务是没有执行的

>这里有一点值得注意的是 我们的 **barrier任务** 是 **异步** 发送的,如果改成 dispatch_barrier_sync(同步)会是什么样子呢? 我们直接看下打印出来的结果吧
>```
打印结果:
2017-01-16 13:56:46.256 xxx[1772:226370] 任务1
2017-01-16 13:56:46.256 xxx[1772:226346] 任务2
2017-01-16 13:56:46.256 xxx[1772:226347] 任务3
(这之间延迟5秒)
2017-01-16 13:56:51.258 xxx[1772:226302] barrier任务
2017-01-16 13:56:51.259 xxx[1772:226302] mark-----1
2017-01-16 13:56:51.259 xxx[1772:226302] mark-----2
2017-01-16 13:56:51.259 xxx[1772:226347] 任务4
2017-01-16 13:56:51.260 xxx[1772:226346] 任务5
2017-01-16 13:56:51.260 xxx[1772:226370] 任务6

这里我们注意的是mark------1 和 mark------2的位置,也就是说 dispatch_barrier_async 会卡住线程因为是同步发送的。

最前面我们提到的死锁又来了

既然dispatch_barrier_async会卡住线程,那么如果所插入的队列是 The main queue(主线程串行队列)

Paste_Image.png

打印结果:
2017-01-16 14:05:47.324 xxx[1810:234790] 我是死锁的上面


#7.Dispatch Apply
>dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API,该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束

dispatch_queue_t applyQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(applyQueue, ^{
NSLog(@"内部执行的任务1 线程%@",[NSThread currentThread]);
sleep(2);
NSLog(@"内部执行的任务2 线程%@",[NSThread currentThread]);
});

dispatch_apply(10, applyQueue, ^(size_t index) {
NSLog(@"我是插入的第%zu次 %@",index,[NSThread currentThread]);
});

2017-01-16 14:11:38.075 xxx[1827:240047] 我是插入的第0次 <NSThread: 0x600000075280>{number = 1, name = main}
2017-01-16 14:11:38.075 xxx[1827:240081] 内部执行的任务1 线程<NSThread: 0x608000263c40>{number = 3, name = (null)}
2017-01-16 14:11:38.075 xxx[1827:240083] 我是插入的第1次 <NSThread: 0x6080002627c0>{number = 4, name = (null)}
2017-01-16 14:11:38.076 xxx[1827:240214] 我是插入的第2次 <NSThread: 0x608000262680>{number = 5, name = (null)}
2017-01-16 14:11:38.076 xxx[1827:240215] 我是插入的第3次 <NSThread: 0x608000264180>{number = 6, name = (null)}
2017-01-16 14:11:38.076 xxx[1827:240047] 我是插入的第4次 <NSThread: 0x600000075280>{number = 1, name = main}
2017-01-16 14:11:38.076 xxx[1827:240083] 我是插入的第5次 <NSThread: 0x6080002627c0>{number = 4, name = (null)}
2017-01-16 14:11:38.076 xxx[1827:240214] 我是插入的第6次 <NSThread: 0x608000262680>{number = 5, name = (null)}
2017-01-16 14:11:38.077 xxx[1827:240047] 我是插入的第8次 <NSThread: 0x600000075280>{number = 1, name = main}
2017-01-16 14:11:38.076 xxx[1827:240215] 我是插入的第7次 <NSThread: 0x608000264180>{number = 6, name = (null)}
2017-01-16 14:11:38.077 xxx[1827:240083] 我是插入的第9次 <NSThread: 0x6080002627c0>{number = 4, name = (null)}
2017-01-16 14:11:40.078 xxx[1827:240081] 内部执行的任务2 线程<NSThread: 0x608000263c40>{number = 3, name = (null)}

>这个单纯的插入任务。。

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

推荐阅读更多精彩内容