技术标签: Objective-C
最近在看AFNetworking3.0源码时,注意到在 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法 (L681),dispatch_semaphore不甚理解,经查原来是通过引入信号量(dispatch_semaphore)的方式把NSURLSession的异步方法 getTasksWithCompletionHandler: 变成了同步方法
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
这里是把本来异步的getTasksWithCompletionHandler方法变成了同步的方式了,通过引入信号量的方式,等待异步方法获取到tasks,然后再返回。
关于信号量,一般可以用停车来比喻。
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal
就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),
调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;
当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主
没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,
所以就一直等下去。
信号量是基于计数器的一种多线程同步机制,用来管理对资源的并发访问。
信号量内部有一个可以原子递增或递减的值。如果一个动作尝试减少信号量的值,使其小于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_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);
如果注掉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);
});
}
做通讯录的时候需要判断权限,才能获取通讯录
//这个变量用于记录授权是否成功,即用户是否允许我们访问通讯录
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);
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);
});
});
}
信号量属于底层工具。它非常强大,但在多数需要使用它的场合,最好从设计角度重新考虑,看是否可以不用。应该优先考虑是否可以使用诸如操作队列这样的高级工具。通常可以通过增加一个分派队列dispatch_suspend,或者通过其他方式分解操作来避免使用信号量。信号量并非不好,只是它本身是锁,能不用锁就不要用。尽量用cocoa框架中的高级抽象,信号量非常接近底层。但有时候,例如需要把异步任务转换为同步任务时,信号量是最合适的工具。
参考:
AFNetworking 源码阅读
iOS GCD之dispatch_semaphore学习
iOS GCD中级篇 - dispatch_semaphore(信号量)的理解及使用
理解这个概念之前,先抛出一个问题
问题描述:
假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理呢?
或者
我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。
定义:
1、信号量:就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。
其实,这有点类似锁机制了,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。
2、信号量主要有3个函数,分别是:
//创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_create(信号量值)
//等待降低信号量
dispatch_semaphore_wait(信号量,等待时间)
//提高信号量
dispatch_semaphore_signal(信号量)
注意,正常的使用顺序是先降低然后再提高,这两个函数通常成对使用。 (具体可参考下面的代码示例)
3、那么就开头提的问题,我们用代码来解决
-(void)dispatchSignal{
//crate的value表示,最多几个资源可访问
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});<br>
//任务2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});<br>
//任务3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
}
执行结果:
总结:由于设定的信号值为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。
这里我们扩展一下,假设我们设定信号值=1
1
|
dispatch_semaphore_create(1)
|
那么结果就是:
如果设定信号值=3
1
|
dispatch_semaphore_create(3)
|
那么结果就是:
其实设定为3,就是不限制线程执行了,因为一共才只有3个线程。
以上只是举的比较简单的例子,在一些特殊场景下,合理利用信号量去控制,能够方便的解决我们的难题哦
摘要本章我们会讨论在Bash脚本中使用条件,包含以下几个话题:if 语句 使用命令的退出状态比较和测试输入和文件if/then/else 结构 if/then/elif/else 结构 使用和测试位置参数嵌套 if 语句 布尔表达式使用 case 语句 7.1. 介绍if
故事前要今天,在做公司的一个项目,需要和第三方公司进行对接,需要将我们采集到的数据发送给第三方公司,按照对方提供的文档,传递好参数后,httpclient.execute(method)请求后,得到的状态码 ,一直是502,犹豫第一次使用HttpClient post json数据,一直怀疑是自己的代码问题,最后不知在哪个技术论坛看到 ,有人问url请求中有空格怎么办,突然发现对方提供的pdf...
存在问题最近在做一个项目A,该项目A是已经是system app,但该App在内存不足、用户清理后台后,进程会死亡。为了保证A能一直处于运行状态,开发一个守护进程用于保活和拉起A。需求:在开机后,A不能自启动,保证设备快速开机运行用户清理后台后,A能继续存活,以便接听实时通话在应用关闭后,A能够保活内存占用过大,系统释放内存后,A和service能存活重启后,service能够自启动......
Fiddler抓取手机app请求工具:fiddler4、Android手机第一步:打开fiddler,进行fiddler的常规设置HTTPS菜单中找到Tools—>Options(fiddler4以下版本的会不叫这个,但是看到Options即是)---->HTTPS---->勾选Decrypt HTTPS traffic,然后此项下拉出现的ignore server certificate errors(unsafe)也勾选,check to certificate revocat
cocos2dx 里的使用引用计数的对象都是继承CCObject的。引擎里大多数提供的类型是使用引用计数的。可以手动release 和retain,分别是计数减和加如果不想手动释放就调用autorelease,该对象会被放到CCPoolManager 内存管理池的当前的释放池里面(内存管理池里有内存池数组,最后创建的内存池就是当前的内存池)。内存管理池是个单例(CCPoolMa
使用IDA进行静态逆向分析,很重要的一点是是对高级语言程序结构的解析,比如Switch-Case结构。IDA的F5功能很多时候会无法生成C语言代码,此时就需要我们自己分析汇编代码,理解switch-case结构等。 从简单入手, 测试使用的C++代码如下: int switch_case(char a,int b,int *c,int d,int e){ switch(a)
redis-cluster集群部署及测试(超详细),傻瓜式照搬
2021/3/25–前端第14天–淘宝导航栏、遮盖、z-index、hack等淘宝导航栏:固定定位<style> .main { width: 1200px; margin: 0 auto; height: 2000px; background-color: pink; } .slider { height: 200px;
20181205 周三 日记9点30点,早餐: 粥,鸡蛋饼1、上午:9点30点开始改orbbec代码2、中饭:豆腐3、中午:睡觉到2、504、下午:改orbbec代码5、晚上:
原文地址:http://till.iteye.com/blog/142984SUSE Linux Enterprise下安装JDK1.6和Tomcat5.51.JDK下载: http://java.sun.com 下载自解压包:jdk-6u17-linux-i586-rpm.bin 赋予权限:chmod 755 jdk-6u17-linux...
大家连接树莓派一般都会使用ssh,但是只有树莓派成功开机之后,我们才可以通过ssh连接树莓派进行控制,如果我们把系统搞挂了,没有成功开机,这样我们完全不知道树莓派在干什么,这个时候接上屏幕看就非常麻烦了。不过我们可以通过串口连接来看树莓派的开机log,帮助我们搞定一些开机中的错误。材料:树莓派,电脑,uart转USB线,电源线树莓派GPIO口如图所示将系统镜像写入SD卡后,进入boot盘,编辑co...
Java初级面试题后端1.HashMap的数据结构在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。2.HashMap和TreeMap的区别HashMap:数组方式存储key/value,线程非安全,允许null...