前言


最近在看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则会返回NULL
dispatch_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(信号量)的更多相关文章

  1. iOS GCD基础篇 - 同步、异步,并发、并行的理解

    1.关于GCD - GCD全称是Grand Central Dispatch  - GCD是苹果公司为多核的并行运算提出的解决方案  - GCD会自动利用更多的CPU内核(比如双核.四核)  - GC ...

  2. iOS GCD中级篇 - dispatch_semaphore(信号量)的理解及使用

    理解这个概念之前,先抛出一个问题 问题描述: 假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理呢? 或者 我们要下载很多图片,并发异步进行,每个下载都会开 ...

  3. iOS中级篇 - dispatch_semaphore(信号量)的理解及使用

    理解这个概念之前,先抛出一个问题 问题描述: 假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理呢? 没错,这里,我们就可以方便的利用信号量来解决这个问题. ...

  4. ios开发GCD(2)-dispatch_semaphore_t信号量计数器

    思考:现在有多个线程异步执行,我们想要同时最多只能执行2个或n个,该怎么办? dispatch_semaphore_t 看代码解析: NSLog(@"开始"); dispatch_ ...

  5. ios GCD的使用及封装

    实现代码: CGDHelper /* * Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法. * 系统要求:iOS4.0以上. */ #import & ...

  6. 【精】iOS GCD 具体解释

    一.介绍 1.什么是GCD? Grand Central Dispatch.是苹果公司开发的一套多核编程的底层API. GCD首次公布在Mac OS X 10.6,iOS4及以上也可用.GCD存在于l ...

  7. iOS GCD使用

    Grand Central Dispatch(GCD)是异步运行任务的技术之中的一个. 一般将应用程序中记述的线程管理用的代码在系统级中实现.开发人员仅仅须要定义想运行的任务并追加到适当的Dispat ...

  8. iOS GCD使用指南

    Grand Central Dispatch(GCD)是异步运行任务的技术之中的一个. 一般将应用程序中记述的线程管理用的代码在系统级中实现.开发人员仅仅须要定义想运行的任务并追加到适当的Dispat ...

  9. iOS GCD 编程小结

    一.简单介绍 1.GCD简介? 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数 2.GCD优势 GCD是苹果公司为多核的并行运算提出的 ...

随机推荐

  1. 翻翻git之---可用作课程表/排班表的自定义table库ScrollTableView

    转载请注明出处:王亟亟的大牛之路 最近一直在写混合开发的东西,是时候温故下native的了. 一年多之前领导提了一个双性滚动+快点击的"TableView"那时候自己整了2 3天没 ...

  2. IDEA 逆向工程

    今天要写一个导入功能,需要把数据库的一个表映射成一个pojo,但是这表字段不是一般的多啊...想了想能不能自动生成???在IDEA中点了半天发现还是可以的 下面介绍一下步骤: 第一步:找到这个图标,建 ...

  3. 推荐一个JavaScript触发器插件,可通过指定频次、指定时间内触发指定的处理函数

    推荐一个JavaScript触发器插件js-trigger js-trigger是一个JavaScript触发器插件,可通过指定频次.指定时间内触发指定的处理函数 https://tanwei-cc. ...

  4. UVA 1213 Sum of Different Primes(经典dp)

    题意:选择k(k<15)个唯一质数,求出和为n(n<1121)的可能数 题解:预处理dp,dp[k][n]表示使用k个素数拼成n的总方案数 就是三重枚举,枚举k,枚举n,枚举小于n的素数 ...

  5. sonar总结--

    maven的setting.xml 配置  https://www.cnblogs.com/javawebsoa/p/3206504.html

  6. 英语词根与单词的说文解字---词根示例1、第10页 st(at)

    英语词根与单词的说文解字---词根示例1.第10页 st(at) 一.总结 一句话总结: 站 state,establish,constitution 英 [ɪ'stæblɪʃ; e-]  美 [ɪˈ ...

  7. 微信小程序------媒体组件(视频,音乐,图片)

    今天主要是简单的讲一下小程序当中的媒体组件,媒体组件包括:视频,音乐,图片等. 先来看看效果图: 1:图片Image <!-- scaleToFill:不保持纵横比缩放图片,使图片的宽高完全拉伸 ...

  8. 初学Laravel框架与ThinkPHP框架的不同

    作为一个PHP菜鸟初学Laravel框架 在学习过程中我发现了其与TP框架的不同点,由于时间问题和认识还不够完善我先写出其中几点,有错误的地方希望各位大牛斧正... 1.渲染模版方式的不同:在Lara ...

  9. nyoj——5字符串STL复习

    Binary String Matching 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 Given two strings A and B, whose alp ...

  10. linux---进程,(rpm,yum)软件包

      3) 为新加的硬盘分区,一个主分区大小为5G,剩余空间给扩展分区,在扩展分区上划分1个逻辑分区,大小为5G fdisk -l fdisk /dev/sdb p 查看 n 新建    p  主分区 ...