GCD

本文章属于原创,转载请注明出处

参考资料:
底层并发API
NSOperation和GCD
iOS高级编程
编写iOS代码的52个有效方法

这篇文章有点乱,当你读到这篇文章时,可以选择不同的小块来进行阅读,因为除了前面对并行串行,队列的介绍是基础以外,别的部分几乎没什么关联

pthread

  • 一种使用极其复杂的线程实现方式

NSThread

  • 对pthread的一种封装

以上,无论是pthread还是NSTread使用起来都相当复杂,在GCD出现后很少会用到

一些概念

并发 & 并行 & 串行

并发是一种现象,表示多个任务同时发生,需要被处理,比如很多人排队等待检票
并行是一种技术,表示同时处理多个任务的技术,比如很多人在不同的窗口等待检票
串行是并行的反义词,表示任务必须按顺序,一个一个执行

同步 & 异步

同步是指等一件事做完了再做另一件
异步是指,函数执行完之后立即返回,但内部执行的任务稍后完成

资源共享

两个线程同时访问一个资源会造成竞态条件,当一个线程正在写入一个资源,而另一个线程尝试访问该资源时,可能会造成访问的资源访问的半新不旧,保护该条件发生的方法是互斥

互斥锁

互斥是说同一时刻,只允许一个线程访问特定的资源,为保证这一点,每个想要访问共享资源的线程,首先要获得一个共享资源的互斥锁,一定一个线程完成了操作,就释放该互斥锁,这样另外一个线程就可以进行访问了

什么是GCD

  • GCD以Block为基本单位,一个Block中的代码可以分为一个任务,任务可以说是执行一个Block,GCD有两个重要的概念,“队列"和"执行方式”
  • 执行Block的过程其实是把Block放到合适的队列里,并选择合适的执行方式去执行Block的过程
  • 队列可以分为三种
  1. 串行队列:先进入队列的任务先出队列,每次只执行一个任务
  2. 并发队列:先进入队列的任务先出,但是可以一起执行
  3. 主队列:特殊的串行队列,队列的任务一定在主线程上执行
  • 执行方式有两种
  1. 同步执行
  2. 异步执行
// GCD简单的使用方式
dispatch_async (queue, ^{
	// 长时间的处理
	dispatch_async(dispatch_get_main_queue(), ^{
		// 主线程的执行
	}
})
// '^' 字表示其中用到了Block

CPU一次只能执行一个命令,不能分开并行的执行两个命令,这种模式即为线程,"上下文切换"使线程看起来像是并行执行

多线程编程可以防止长时间的处理妨碍主线程的运行,会妨碍主线程中叫RunLoop的主循环的运行,导致用户界面,应用程序的画面长时间停滞

GCD里的死锁

以下代码会引起死锁

viewDidLoad() {
	super.viewDidLoad()
	let mainQueue = dispatch_get_main_queue()
	let block = { print(NSTread.currentThread) }
	dispatch.sync(mainQueue, block)
}
  • 在这个例子里,主队列在执行dispatch_sync,随后队列里新增一个任务Block,因为主队列是同步队列,所以Block要等dispatch_sync执行完才能执行,但是dispatch_sync是同步派发,要等Block执行完才能结束,所以造成了死锁
  • 只有在确定了任务的执行顺序时,必要的情况下,才使用sync函数。一般情况下都使用async

GCD的API

Dispatch Queue
dispatch_async (queue, ^{
	// 执行的任务
});

使用Block语法,定义想执行的任务,通过dispatch_async函数,追加复制在变量queue的Dispatch Queue中,这样就可以使指定的Block在另一个线程中执行
Dispatch Queue是执行处理的等待队列,通过dispatch_async函数等,在Block语法中,记述想执行的处理并追加到Dispatch Queue中,Dispatch Queue按照追加顺序执行处理FIFO

有两种Dispatch Queue

  • Serial Dispatch Queue等待当前执行的进程执行
  • Concurrent Dispatch Queue不等待,并行执行,多个线程同时运行
dispatch_queue_create
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.exmpl.gcd.mySerialDispatchQueue", NULL);
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.myConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT");
// 第一个参数指定名称
// 第二个参数指定为一个Concurrent queue
  • 多个线程使用一个资源时,会造成数据竞争,这样可以使用Serial Dispatch Queue,要避免生成大量的Serial Dispatch Queue
  • 当想并行执行不发生数据竞争等问题的处理时,使用Concurrent Dispatch Queue,对于Concurrent Dispatch Queue来说,不管生成多少,XNU内核只使用有效管理的线程

通过dispatch_queue_crete生成的Dispatch Queue不能使用ARC

dispatch_release(mySerialDispatchQueue);
dispatch_retain(myConcurrentDispatchQueue);
Main Dispatch Queue/Global Dispatch Queue
  • Main Dispatch Queue

系统提供Main Dispatch Queue和Global Dispatch Queue,不用自己提供

因为主线程只有一个,所以Main Dispatch Queue是Serial Dispatch Queue,追加到主线程的处理在主线程的RunLoop中执行,由于在主线程执行,因此要将用户界面更新等一些必须在主线程中执行的方法在Main Dispatch Queue中使用

  • Global Dispatch Queue
    Global Dispatch Queue是所有程序都能使用的Concurrent Dispatch Queue,有四个优先级

各Dispatch Queue的获取方法如下

dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

对Main Dispatch Queue和Global Dispatch Queue执行retain,release操作不会有任何变化,强烈建议大多数情况下使用默认的优先级

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
	dispatch_async(dispatch_get_main_queue(), ^ {
		
	});
});
dispatch_set_target_queue
  • 改变Dispatch Queue的优先级
dispatch_after

在指定时间后追加处理到Dispatch Queue

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
// ull是数字字面量,表示unsigned long long

真正的追加时间会有所延迟

第一个参数是指定时间用的dispatch_time_t类型的值,该函数从第一个参数开始,从第二个参数后结束

Dispatch Group

在追加到Dispatch Queue中的多个处理全部结束后执行结束处理

如下,追加三个Block到Global Dispatch Queue里,当这些Block全部执行完毕,就会执行Main Dispatch Queue中结束处理用的Block

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY, 0);
dispatch_group_t group = dispatch_group_create();

// 与dispatch_async相同,但group为第一个参数,指定的Block属于制定的Dispatch Group
dispatch_group_async(group, queue, ^{ NSLog(@"blk0"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk1"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk2"); });

dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"done")l });
dispatch_release(group);
// 执行release操作

向Global Dispatch Queue即Concurrent Dispatch Queue追加处理,多个线程并行执行,所以追加处理的执行顺序不定,执行时会发生变化,但done一定是最后输出的

dispatch_barrier_async
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.barrier", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_async(queue, blk2_for_reading);
dispatch_async(queue, blk3_for_reading);
// 写入处理
dispatch_barrier_async(queue, blk_for_writing);
// 将写入的内容读取之后的处理中
dispatch_async(queue, blk4_for_reading);
dispatch_async(queue, blk5_for_reading);
dispatch_async(queue, blk6_for_reading);
dispatch_async(queue, blk7_for_reading);

当在blk3和blk4之间写入时,会发生数据竞争,此时使用dispatch_barrier_async函数,会让blk3之后的写入操作执行完,再执行blk4

dispatch_sync
  • async意味着非同步,dispatch_async函数不做任何等待

  • sync会等待Block执行结束

dispatch_sync函数可以说是简易版的dispatch_group_wait函数,下面的源码都会引起死锁

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{ NSLog(@"Hello?"); });

该源码在Main Dispatch Queue即主线程中执行指定的Block,并等待结束,而其实主线程正在执行执行源代码,所以无法执行追加到Main Dispatch Queue上的Block

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
	dispatch_sync(queue, ^{ NSLog(@"Hello?); });
});
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue"), NULL);
dispatch_async(queue, ^{
	dispatch_sync(queue, ^{ NSLog(@"Hello?"); });
});
dispatch_apply

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

dispatch_queue_t queue = dispatch_queue_get_global_queue(DISPATCH_QUEUE_PRIORITY, 0,);
dispatch_apply(10, queue, ^(size_t index) {
	NSLog(@"%zu", index);
});
NSLog(@"done");

因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定,但是输出结果中最后的done必定在最后的位置,因为dispatch_apply函数会等待全部处理执行结束

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^ {
	dispatch_apply([array count], queue, ^(size_t index) {
		NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
	});
	dispatch_async(dispatch_get_main_queue(), ^{
		NSLog(@"done");
	});
});
dispatch_suspend / dispatch_resume

dispatch_suspend会挂起Dispatch Queue,适当的时候再恢复

dispatch_suspend(queue);

dispatch_resume会恢复指定的Dispatch Queue

dispatch_resume(queue);
Dispatch Semaphore
  • 同一资源在被不同线程访问的时候,可能被无意间改变,得到期望以外的值,而防止这种情况发生的方法之一就叫做信号量
  • Serial Dispatch Queue 和 dispatch_barrier_async函数可避免这类问题,不过也可以更小粒度的控制

信号量的机制就相当于,在租房子的时候,没有租到房子的人是不能住进去的,所以需要上锁,此时信号量就相当于最多能租几间房子,当能出租的房间数量为0的时候,想要租房间的人就要在租房名单上排队等候,等到房间里租房的人不再续租了才可以入住,入住顺序就是名单上的顺序,而不再入住的人还要告知房东,并安排接下来的人入住,不能一走了事

// 创建信号量(买好房子,有几个房间)
var semaphore = dispatch_semaphore_create(2)

// wait表示一直等待直到信号量的值大于等于1,执行这个方法后会把信号量的值 -1
// 入住一个人
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

// 把此时信号量的值加1
// 有人不再续租了,可入住的房间空出来
dispatch_semaphore_signal(semaphore)
var semaphore = dispatch_semaphore_create(1)
let queue = dispatch_queue_create("com.gcd", DISPATCH_QUEUE_CONCURRENT)
var array = [Int]()

for i in 0...10000 {
	dispatch_async(queue, { () -> in 
		// 线程执行到这里,如果信号值为1,那么wait方法返回1,开始执行接下来的操作
		// 同时,因为信号量变成了0,其它执行到这里的线程都必须等待
		dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
		// 执行了wait方法之后,信号量的值变成了0,可以进行接下来的操作
		// 这时候其它线程都会等待wait方法返回
		// 可以修改Array的线程在任意时刻都只有一个
		array.append(i)
		// 排他操作结束,要记得调用signal方法,把信号值加一
		// 这样,如果有别的线程在等待wait函数返回,就由最先等待的线程先执行
		dispatch_semaphore_signal(semaphore)
	}
}
dispatch_once

保证在程序执行中只执行一次处理,单例模式,在生成单例对象时使用,完全线程安全

+ (id)sharedInstance {
	static EOCClass *sharedInstance = nil;
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		sharedInstance = [[self alloc] init];
	});
	return sharedInstance;
}

要确保dispatch_once_t被声明为static或者别的全局作用域,也不要把dispatch_once_t作为成员变量

使用dispatch_once可以简化代码并彻底保证线程安全,开发者无需担心加锁或同步,所有问题都由GCD在底层处理,由于每次调用都必须产出完全相同的标记,所以标记要申明成static,该变量在static的作用域中,可以保证编译器每次执行sharedInstance方法都会调用这个变量,不会产生新的,dispatch_once也更高效

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值