GCD

GCD

概念

GCD 全称Grand Central Dispatch,是一套C语言API,提供了⼀种新的方法来进⾏并发程序编写,因此更底层更高效.

特点:

  • 可用于多核的并行运算,会自动利用更多的CPU内核(比如双核、四核)
  • 自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 只需要告诉GCD什么任务,以哪种方式(同步,异步)来提交到哪种队列中执行,不需要编写任何线程管理代码
  • 因为是C语言的,更多是以函数调用的方式,且大多以dispatch开头.

在Swift中可以无缝使用GCD的API的,而且得益于闭包特性的加入,使用起来比之前在Objective-C中更加简单方便,Swift3中更是抛弃了传统的基于C的GCDAPI,采用了更为先进的书写方式。

线程

一个CPU一次只能执行一个命令,不能执行某处分开的并列的两个命令,因此通过CPU执行的CPU的命令列就好比一条无分叉的大道,其执行不会出现分歧,这条"一个CPU执行的CPU命令列为一条无分叉的路径"即为线程.
当这条执行路径有多条时,即为多线程

多线程的缺点

  1. 多个线程更新相同的资源导致数据不一致(数据竞争)
  2. 多个线程相互等待(死锁)
  3. 线程开辟太多消耗大量内存

任务和队列

任务:要执行的操作或方法(一个操作、一个函数、一个方法等等),其实就是一段代码,在实际的开发中大多是以block(dispatch_block_t;Swift是对block进行了一层封装可以以类对象DispatchWorkItem来进行提交)的形式,使用起来更加灵活。
队列:存放任务的集合,将任务添加到队列然后执行,GCD会自动将队列中的任务按(FIFO)先进先出的方式取出并交给对应线程执行。

队列类型

串行队列:任务按先后顺序逐个执行,需要等待前面的任务执行完毕再执行新的任务,每一个串行队列使用一个线程,但我们可以生成多个串行队列来做并行处理
并行队列:多个任务按添加顺序一起开始执行,不用等待前面的任务执行完毕再执行新的任务,并行队列由XNU内核使用有效管理的线程.
GCD提供的队列有三种:主队列,全局队列,自定义队列

主队列:主队列是串行队列,追加的处理在主线程的Runloop中执行,所以要将用户界面的更新的响应事件等一些必须在主线程中执行的处理追加到主队列,可以通过dispatch_get_main_queue()来获取。

//Objective-C
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadUI];
});

//swift
DispatchQueue.main.async(execute: {
self.reloadUI
})

全局队列:全局并发队列(Concurrent Dispatch Queue),GCD默认提供了全局的并发队列,可以通过dispatch_get_global_queue()获取。当然可以指定其优先级.4个执行优先级,High,Default,Low,Background;通过XNU内核用于GlobalDispatchQueue的线程并不能保证实时性,因此执行优先级只是大致的判断.

dispatch_queue_t
dispatch_get_global_queue(long identifier, unsigned long flags);

//swift
DispatchQueue.global(qos: .default)

对应优先级如下
|Objective-C|Swift|
|DISPATCH_QUEUE_PRIORITY_HIGH: | .userInitiated|
|DISPATCH_QUEUE_PRIORITY_DEFAULT: | .default|
|DISPATCH_QUEUE_PRIORITY_LOW: | .utility|
|DISPATCH_QUEUE_PRIORITY_BACKGROUND: | .background|

自定义队列:
自定义队列通过传入队列的标签和队列类型,甚至是优先级等其它参数来配置我们需要的任务队列.

  • label:提供一个独一无二的标签。苹果建议使用一个反向的DNS符号(com.apple.myqueue),因为用它很容易创造一个独一无二的标签,但你可以使用你喜欢的任何字符串,只要这个字符串是唯一的
  • dispatch_queue_attr_t:用以标识队列串行,并行,以及优先级等信息
    Swift3中GCD对其进行了细分,如DispatchQoS(优先级),Attributes(描述),AutoreleaseFrequency(自动释放频率),target(目标队列)

自定义串行队列

 dispatch_queue_t queue = dispatch_queue_create("com.queue.serial", DISPATCH_QUEUE_SERIAL);
 
 //swift
 let queue = DispatchQueue(label: "com.queue.serial")

自定义并行队列

dispatch_queue_t queue = dispatch_queue_create("com.queue.concurrent", DISPATCH_QUEUE_CONCURRENT);

//swift
let queue = DispatchQueue(label: "com.queue.concurrent",qos: .userInteractive, attributes: .concurrent, autoreleaseFrequency: .never, target: nil)

自定义队列的优先级:可以通过dipatch_queue_attr_make_with_qos_class或dispatch_set_target_queue方法

不指定优先级时,dispatch_queue_create函数生成的队列(不管是serial还是Concurrent)都使用和默认优先级Global Dispatch Queue相同执行优先级的线程.
dispatch_set_target_queue的第一个参数为要设置优先级的queue,第二个参数是对应的优先级队列作为参照,第一个参数不要指定系统提供的主队列和全局队列,因为不知道会出现什么情况.
dispatch_set_target_queue用来设置队列的优先级之外,还能够创建队列的层次体系,当我们想让不同队列中的任务同步执行时,我们可以创建一个串行队列,然后将这些队列的target指向这个队列

dispatch_queue_t serialQueue = dispatch_queue_create("com.target.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t firstQueue = dispatch_queue_create("com.target.firstqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t secondQueue = dispatch_queue_create("com.target.secondqueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_set_target_queue(firstQueue, serialQueue);
dispatch_set_target_queue(secondQueue, serialQueue);

dispatch_async(firstQueue, ^{
NSLog(@"1");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"1 end");
});
dispatch_async(secondQueue, ^{
NSLog(@"2");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"2 end");
});
dispatch_async(secondQueue, ^{
NSLog(@"3");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"3 end");
});
打印结果:
[2185:738525] end!
[2185:738579] 1
[2185:738579] 1 end
[2185:738579] 2
[2185:738579] 2 end
[2185:738579] 3
[2185:738579] 3 end

任务执行方式

同步执行或异步执行
同步意味着任务执行完毕才返回,任务执行完毕之前,会一直等待;异步意味着任务立即返回,不做任何等待
同步任务的执行方式一般以sync开头,异步任务的执行方式一般以async开头

dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self work];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self work];
});

延迟执行

dispatch_after不是在指定时间后执行处理,而是在指定时间用dispatch_async追加处理到Diapatch Queue.
延迟执行的时间可以用dispatch_time(DispatchTime)或者dispatch_wallTime来表示;dispatch_time是相对时间,一般在当前时间进行追加,dispatch_wallTime是绝对时间,由POSIX中的timespec类型的时间获取.

//Objective-c
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3ull * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"after 3 s!");
});

//Swift
/// 延时调用
///
/// - Parameter timeInterval: 秒
func asynAfter(_ timeInterval:TimeInterval){
////dispatch_time用于计算相对时间,当设备睡眠时,dispatch_time也就跟着睡眠了.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeInterval) {
print("\(timeInterval)s after!")
}
}

func asynAfterWalltime(_ timeInterval:TimeInterval){

//dispatch_walltime用于计算绝对时间。
let delaytimeInterval = Date().timeIntervalSinceNow + 2.0
let nowTimespec = timespec(tv_sec: __darwin_time_t(delaytimeInterval), tv_nsec: 0)
let delayWalltime = DispatchWallTime(timespec: nowTimespec)

////wallDeadline需要一个DispatchWallTime类型。创建DispatchWallTime类型,需要timespec的结构体。
DispatchQueue.main.asyncAfter(wallDeadline: delayWalltime) {
Thread.current.name = "dispatch_Wall_time_Thread"
print("Thread Name: \(String(describing: Thread.current.name))\n dispatchWalltime: Deplay \(delaytimeInterval) seconds.\n")
}
}

Group

Groupde 的职责就是当队列中的所有任务都执行完毕后,会发出一个通知来告诉告诉大家,任务组中所执行的队列中的任务执行完毕了。可以有手动和自动两种方式
一般用于监听并行队列中多个处理全部结束后再执行的任务

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(@"task 1");
sleep(2);
NSLog(@"task 1 end!");
});

dispatch_group_async(group, queue, ^{
NSLog(@"task 2");
sleep(3);
NSLog(@"task 2 end!");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"reload UI");
});

也可以使用diapatch_group_wait来等待组队列中所有的任务结束,再做处理.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER),DISPATCH_TIME_FOREVER表示永久等待,函数会在执行的线程里造成阻塞,返回0表示所以任务执行完毕.

手动管理任务的结束

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(@"task 1");
sleep(2);
NSLog(@"task 1 end!");
dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task 2");
sleep(3);
NSLog(@"task 2 end!");
dispatch_group_leave(group);
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"reload UI");
});

栅栏函数barrier

dispatch_barrier_sync/async会等待追加到ConcurrentDispatchQueue上的并行执行的处理全部结束之后,再将之后的处理追加到该队列之后.

//防止文件读写冲突,可以创建一个串行队列,操作都在这个队列中进行,没有更新数据读用并行,写用串行。
dispatch_queue_t dataQueue = dispatch_queue_create("com.starming.gcddemo.dataqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dataQueue, ^{
[NSThread sleepForTimeInterval:2.f];
NSLog(@"read data 1");
});
dispatch_async(dataQueue, ^{
NSLog(@"read data 2");
});
//等待前面的都完成,在执行barrier后面的
dispatch_barrier_async(dataQueue, ^{
NSLog(@"write data 1");
[NSThread sleepForTimeInterval:1];
});
dispatch_async(dataQueue, ^{
[NSThread sleepForTimeInterval:1.f];
NSLog(@"read data 3");
});
dispatch_async(dataQueue, ^{
NSLog(@"read data 4");
});

dispatch_apply

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [[NSMutableArray alloc]init];
//1.重复次数
//2.追加的目标队列
//3.追加的任务

//dispatch_apply会等待执行任务结束
dispatch_apply(20, queue, ^(size_t i){
NSLog(@"%d",i);
});
NSLog(@"%@",array);

信号量

Semaphore是传统计数信号量的封装,用来控制资源被多任务访问的情况。类似于过马路时的手旗,可以通过时举出手旗,不可以通过时放下手旗.使用计数进行控制,计数为0时等待,大于0时不等待

//创建semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

NSMutableArray *array = [[NSMutableArray alloc]init];
for (int i = 0; i < 10000; i++) {
dispatch_async(queue, ^{

//一直等待知道semaphore的计数值大于0时,此时对计数器进行减1,并返回
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

[array addObject:@(i)];

//对计数器加1
dispatch_semaphore_signal(semaphore);
});
}

dispatch_once

dispatch_once函数能保证某段代码在程序运行过程中只被执行1次.
使用场景:

  • 单例模式
  • 一次性的操作 比如方法交换
static Singleton *_class;
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_class = [[Singleton alloc]init];
});
return _class;
}

Swift3移除了dispatch_once,但其书写单例的方式更加简洁

final class SingletonClass{
static let shared = SingletonClass()
private init(){}
}
  1. 使用final,将这个单例类终止继承。
  2. 全局变量,所以只会创建一次。
  3. 设置初始化方法为私有,避免外部对象通过访问init方法创建单例类的实例。

队列的管理和任务的管理

可以通过dispatch_suspend挂起指定的队列(但对已经处理的任务没有影响),对应的恢复函数dispatch_resume.
其实我们也可以通过任务block对其进行管理,通过dispatch_block_create或者dispatch_block_create_with_qos_class创建一个任务,通过下面操作函数对其管理.

等待任务执行完毕,会根据传入的时间进行阻塞,执行完毕,返回0,超时还未执行完毕返回其它数值

long dispatch_block_wait(dispatch_block_t block, dispatch_time_t timeout);

iOS8+支持任务的取消,正在执行的任务不会受到影响.

dispatch_block_cancel(dispatch_block_t block);

任务完成时,在指定的队列执行另一个任务

dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue,
dispatch_block_t notification_block);

定时器

NSTimer在主线程的runloop里会在runloop切换其它模式时停止,所以我们需要将定时器添加到kCFRunLoopCommonModes,或者手动在子线程开启一个模式为NSRunLoopCommonModes的runloop,其实还可以用不跟runloop关联的dispatch source timer.

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
NSLog(@"Time flies.");
});
//允许100毫秒延迟
dispatch_source_set_timer(source, DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC,100ull * NSEC_PER_MSEC);
dispatch_resume(source);
func timer(){

let timer = DispatchSource.makeTimerSource()

timer.setEventHandler {

print("Timer fired at \(Date())")
}

timer.setCancelHandler {
print("Timer canceld at \(Date())")
}
print("Timer \(Date())")
timer.schedule(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1), repeating: 1.0, leeway:DispatchTimeInterval.microseconds(10) )

print("Timer resume at \(Date())")

timer.resume()

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 60) {
timer.cancel()
}
}

死锁

死锁通常是当多个线程在相互等待着对方的结束时,就会发生死锁.
最常见的死锁一般都是因为sync同步任务导致
主线程使用同步

NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});

在串行队列的异步任务中又提交同步任务到该队列

dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

因为串行队列同时只能执行一个任务,且FIFO,任务2先被提交到队列中,所以任务3会等待任务2执行完毕,而任务3又是任务2的组成部分,造成死锁

回到主线程发现是死循环就没法执行了.

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

推荐阅读更多精彩内容