iOS GCD之dispatch_semaphore(信号量)
来源:互联网 发布:加拿大 留学 专业 知乎 编辑:程序博客网 时间:2024/04/29 20:57
前言
最近在看AFNetworking3.0源码时,注意到在 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法 (L681),dispatch_semaphore不甚理解,经查原来是通过引入信号量(dispatch_semaphore)的方式把NSURLSession的异步方法 getTasksWithCompletionHandler: 变成了同步方法
这里是把本来异步的getTasksWithCompletionHandler方法变成了同步的方式了,通过引入信号量的方式,等待异步方法获取到tasks,然后再返回。
dispatch_semaphore
信号量是基于计数器的一种多线程同步机制,用来管理对资源的并发访问。
信号量内部有一个可以原子递增或递减的值。如果一个动作尝试减少信号量的值,使其小于0,那么这个动作将会被阻塞,直到有其他调用者(在其他线程中)增加该信号量的值。
信号量就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。
其实,这有点类似锁机制了,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。
简单来讲 信号量为0则阻塞线程,大于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步。
基于此dispatch_semaphore主要应用于两个方面 :
1. 保持线程同步
2. 为线程加锁
当然在NSoperation下可以直接设置并发数,就没有这么麻烦了。
我们使用GCD的时候如何让线程同步,也有多种方法
1.dispatch_group
2.dispatch_barrier
3.dispatch_semaphore
dispatch_semaphore相关的3个函数
// 创建信号量,参数:信号量的初值,如果小于0则会返回NULLdispatch_semaphore_t dispatch_semaphore_create(long value);// 等待降低信号量,接收一个信号和时间值(多为DISPATCH_TIME_FOREVER)// 若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;// 若信号量大于0,则会使信号量减1并返回,程序继续住下执行long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);// 提高信号量, 使信号量加1并返回long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
在dispatch_semaphore_wait和dispatch_semaphore_signal这两个函数中间的执行代码,每次只会允许限定数量的线程进入,这样就有效的保证了在多线程环境下,只能有限定数量的线程进入。
可用于处理在多个线程访问共有资源时候,会因为多线程的特性而引发数据出错的问题。
应用场景
保持线程同步,将异步操作转换为同步操作
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block int j = 0; dispatch_async(queue, ^{ j = 100; dispatch_semaphore_signal(semaphore); }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"finish j = %zd", j);
结果输出 j = 100;
如果注掉dispatch_semaphore_wait这一行,则 j = 0;
注释: block块异步执行添加到了全局并发队列里,所以程序在主线程会跳过block块(同时开辟子线程异步执行block块),执行块外的代码dispatch_semaphore_wait,因为semaphore信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会阻塞当前线程(主线程),进而只执行子线程的block块,直到执行块内部的dispatch_semaphore_signal使得信号量+1。正在被阻塞的线程(主线程)会恢复继续执行。这样保证了线程之间的同步。
为线程加锁
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);for (int i = 0; i < 100; i++) { dispatch_async(queue, ^{ // 相当于加锁 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"i = %zd semaphore = %@", i, semaphore); // 相当于解锁 dispatch_semaphore_signal(semaphore); });}
注释:当线程1执行到dispatch_semaphore_wait这一行时,semaphore的信号量为1,所以使信号量-1变为0,并且线程1继续往下执行;如果当在线程1NSLog这一行代码还没执行完的时候,又有线程2来访问,执行dispatch_semaphore_wait时由于此时信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会一直阻塞线程2(此时线程2处于等待状态),直到线程1执行完NSLog并执行完dispatch_semaphore_signal使信号量为1后,线程2才能解除阻塞继续住下执行。以上可以保证同时只有一个线程执行NSLog这一行代码。
获取通讯录
做通讯录的时候需要判断权限,才能获取通讯录
//这个变量用于记录授权是否成功,即用户是否允许我们访问通讯录 int __block tip=0; //创建通讯簿的引用 ABAddressBookRef addressBooks=ABAddressBookCreateWithOptions(NULL, NULL); //创建一个初始信号量为0的信号 dispatch_semaphore_t sema=dispatch_semaphore_create(0); //申请访问权限 ABAddressBookRequestAccessWithCompletion(addressBooks, ^(bool granted, CFErrorRef error) { //granted为YES是表示用户允许,否则为不允许 if (!granted) { tip=1; } //发送一次信号 dispatch_semaphore_signal(sema); }); //等待信号触发 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); CFRelease(addressBooks);
使用 Dispatch Semaphore 控制并发线程数量
void dispatch_async_limit(dispatch_queue_t queue,NSUInteger limitSemaphoreCount, dispatch_block_t block) {//控制并发数的信号量 static dispatch_semaphore_t limitSemaphore; //专门控制并发等待的线程 static dispatch_queue_t receiverQueue; //使用 dispatch_once而非 lazy 模式,防止可能的多线程抢占问题 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ limitSemaphore = dispatch_semaphore_create(limitSemaphoreCount); receiverQueue = dispatch_queue_create("receiver", DISPATCH_QUEUE_SERIAL); }); // 如不加 receiverQueue 放在主线程会阻塞主线程 dispatch_async(receiverQueue, ^{ //可用信号量后才能继续,否则等待 dispatch_semaphore_wait(limitSemaphore, DISPATCH_TIME_FOREVER); dispatch_async(queue, ^{ !block ? : block(); //在该工作线程执行完成后释放信号量 dispatch_semaphore_signal(limitSemaphore); }); });}
注释:以上栗子有点像-[NSOperationQueue maxConcurrentOperationCount]。 在能保证灵活性的情况下,通常更好的做法是使用操作队列,而不是通过GCD和信号量来构建自己的解决方案。
信号量属于底层工具。它非常强大,但在多数需要使用它的场合,最好从设计角度重新考虑,看是否可以不用。应该优先考虑是否可以使用诸如操作队列这样的高级工具。通常可以通过增加一个分派队列dispatch_suspend,或者通过其他方式分解操作来避免使用信号量。信号量并非不好,只是它本身是锁,能不用锁就不要用。尽量用cocoa框架中的高级抽象,信号量非常接近底层。但有时候,例如需要把异步任务转换为同步任务时,信号量是最合适的工具。
参考:
AFNetworking 源码阅读
iOS GCD之dispatch_semaphore学习
iOS GCD中级篇 - dispatch_semaphore(信号量)的理解及使用
iOS开发系列–并行开发其实很容易
- iOS GCD之dispatch_semaphore(信号量)
- iOS --- GCD 信号量控制并发 (dispatch_semaphore)
- iOS学习之GCD 信号量详解,dispatch_semaphore、NSOperationQueue
- IOS开发之GCD---dispatch_semaphore
- GCD 信号量控制并发 (dispatch_semaphore)
- GCD 信号量控制并发 (dispatch_semaphore)
- GCD 信号量控制并发 (dispatch_semaphore)
- GCD 信号量控制并发 (dispatch_semaphore)
- GCD 信号量控制并发 (dispatch_semaphore)
- GCD 第四篇 dispatch_semaphore(信号量)
- GCD dispatch_semaphore 信号量
- GCD(二) ---- dispatch_semaphore 信号量
- iOS学习笔记74-完整详解GCD系列(四)dispatch_semaphore(信号量)
- iOS多线程开发—— GCD dispatch_semaphore 信号量
- iOS之GCD再谈(dispatch_group,dispatch_semaphore)
- iOS信号量-dispatch_semaphore
- 完整详解GCD系列(四)dispatch_semaphore(信号量)
- 完整详解GCD系列(四)dispatch_semaphore(信号量)
- 总结写的stm32的KEY控制LED
- python 统计文本文件中单词出现的个数
- iOS中数组字典里面在放数组字典
- 数据结构--二叉树的便遍历
- HDU4920:Matrix multiplication(思维 & bitset)
- iOS GCD之dispatch_semaphore(信号量)
- poj 2251 Dungeon Master
- jsoncpp封装和解析字符串、数字、布尔值和数组
- 注解(Annotation)自定义注解入门
- 写Python爬虫的准备工作
- ASP.NET 判断是否是连续的数字(可重复,但必须是连续的数字)
- android 实现图标 (图片)拖拽移动
- mook 离港篇之 引用 与 const
- 自动无限轮播图,支持多种自定义效果