iOS-关于GCD信号量那些事儿
随便说说
其实gcd大家都有接触过,也不在解释gcd是什么,为什么突然想说信号量问题,最近这几次面试,当我问到面试者怎么处理多个请求完成后的一系列操作时,有的说造一个临时变量的做追加,其实这样可以,也算是信号量的基本逻辑,有的说用线程做延时操作,怎么延时,怎么操作说的不清楚,有少部分会提到gcd信号量,但是可能说不出来怎么操作,通过信号量的增加与递减,进行网络的并发请求,最后再做网络请求完成后的最终处理;其实实际上大家在做的时候,在网上一搜,基本都能找到;
gcd信号量的应用场景,一般是控制最大并发量,控制资源的同步访问,如数据访问,网络同步加载等。
需求1:多个网络请求完成后(无序)执行下一步
先看下如果不用gcd线程组或信号量会怎么执行
- (void)dispatchsyncsignal{ nsstring *urlstring = @"http://www.baidu.com"; nsurl *url = [nsurl urlwithstring:urlstring]; nsurlrequest *request = [nsurlrequest requestwithurl:url]; nsurlsession *session = [nsurlsession sharedsession]; for (int i=0; i<5; i++) { nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { nslog(@"请求回调 %d---%d",i,i); }]; [task resume]; } nslog(@"end"); }
运行后后台打印输出:
从上面两次打印结果看出,end 先执行,由于网络请求的异步回调,然后各个网络请求的回调顺序是无序的。下面针对需求进行操作;
使用gcd的线程组 dispatch_group_t
- (void)dispatchsyncsignal1{ //创建线程组 dispatch_group_t group = dispatch_group_create(); nsstring *urlstring = @"http://www.baidu.com"; nsurl *url = [nsurl urlwithstring:urlstring]; nsurlrequest *request = [nsurlrequest requestwithurl:url]; nsurlsession *session = [nsurlsession sharedsession]; for (int i=0; i<5; i++) { dispatch_group_enter(group); nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { nslog(@"请求回调 %d---%d",i,i); dispatch_group_leave(group); }]; [task resume]; } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ nslog(@"end"); }); }
运行后后台打印输出:
从两次打印输出结果可以看出,end 是在所有网络请求之后才输出,符合了我们的需求。然后说下用到的相关方法:
dispatch_group_create(); 创建一个dispatch_group_t;
dispatch_group_enter(); 每次网络请求前调用;
dispatch_group_leave(); 每次网络请求后调用;
dispatch_group_enter(); 和 dispatch_group_leave(); 必须配合使用,有几次enter就要有几次leave;
然后当所有dispatch_group_enter(); 的 block 都 dispatch_group_leave(); 后,会执行dispatch_group_notify的block。
使用gcd的信号量 semaphore_t
- (void)dispatchsyncsignal2{ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); nsstring *urlstring = @"http://www.baidu.com"; nsurl *url = [nsurl urlwithstring:urlstring]; nsurlrequest *request = [nsurlrequest requestwithurl:url]; nsurlsession *session = [nsurlsession sharedsession]; __block nsinteger count = 0; for (int i=0; i<5; i++) { nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { nslog(@"请求回调 %d---%d",i,i); count = count + 1; if (count == 5) { dispatch_semaphore_signal(semaphore); count = 0; } }]; [task resume]; } dispatch_semaphore_wait(semaphore, dispatch_time_forever); dispatch_async(dispatch_get_main_queue(), ^{ nslog(@"end"); }); }
运行后后台打印输出:
从两次打印输出结果可以看出,end 也是在所有网络请求之后才输出,也符合了我们的需求。然后说下用到的相关方法:
dispatch_semaphore 信号量如果计数为0,则等待。dispatch_semaphore_signal(semaphore)为计数+1,dispatch_semaphore_wait(sema, dispatch_time_forever)为设置等待时间,这里设置的等待时间是永远等待。对于以上代码通俗一点讲就是,开始为0,等待,等5个网络请求都完成了,计数+1,然后计数-1返回,程序继续执行,count变量,记录网络回调的次数,回调5次之后再发信号量,使后面程序继续运行。
需求2:多个网络请求完成后(按顺序)执行下一步
如果按照需求1的方式,让多个网络请求按顺序执行完后,再进行下一步操作,那又应该怎么执行,当然还可以用信号量来操作:
- (void)dispatchsignal3{ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); nsstring *urlstring = @"http://www.baidu.com"; nsurl *url = [nsurl urlwithstring:urlstring]; nsurlrequest *request = [nsurlrequest requestwithurl:url]; nsurlsession *session = [nsurlsession sharedsession]; for (int i=0; i<5; i++) { nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { nslog(@"请求回调 %d---%d",i,i); dispatch_semaphore_signal(semaphore); }]; [task resume]; dispatch_semaphore_wait(semaphore, dispatch_time_forever); } dispatch_async(dispatch_get_main_queue(), ^{ nslog(@"end"); }); }
运行后后台打印输出:
从两次打印输出结果可以看出,所有网络请求按顺序依次执行,end 在所有请求完成后输出,符合了我们的需求。
简单说说
下面再通过做一个基于 nsobject 的生产者、消费者的 xkgraderoom 工作间,看下信号量的用法,我们可以给生产间定一个最大生产量(这里定2个),毕竟生产间也不能无限制的生产,通过生产者与消费者关系,合理的对生产间的产量进行把控,在产量达到最大产量时,就停止生产,等待消费者消费。
xkgraderoom.h
#import <foundation/foundation.h> /** 生产消费工作间 */ @interface xkgraderoom : nsobject /** 生产 */ - (void)xk_produce:(nsstring *)sp; /** 消费 @return nsstring */ - (nsstring*)xk_comsumer; @end
xkgraderoom.m
#import "xkgraderoom.h" @interface xkgraderoom() /** 仓库 */ @property(strong,nonatomic) nsmutablearray* basearray; /** 访问仓库(临界区)的互斥访问信号量 */ @property(strong,nonatomic) dispatch_semaphore_t criticalsemaphore; /** 消费者-是否消费仓库对象的标记 */ @property(strong,nonatomic) dispatch_semaphore_t comsumersemaphore; /** 生产者-是否生产对象的标记 */ @property(strong,nonatomic) dispatch_semaphore_t productsemaphore; /** 仓库装载最大量 */ @property(nonatomic,assign) int maxproductcount; @end @implementation xkgraderoom - (instancetype)init{ self = [super init]; if (self) { [self setup]; } return self; } - (void)setup{ _maxproductcount = 2; self.basearray = [nsmutablearray array]; self.productsemaphore = dispatch_semaphore_create(_maxproductcount); self.comsumersemaphore = dispatch_semaphore_create(0); //初始化临界区互斥访问信号量,用信号量实现互斥,特殊初始值为1. //控制同一时刻只有一个线程对象在访问仓库 self.criticalsemaphore = dispatch_semaphore_create(1); } /** 生产 */ -(void)xk_produce:(nsstring *)sp{ //先获取访问仓库的信号量 long basecount = dispatch_semaphore_wait(self.criticalsemaphore, 5 * nsec_per_sec); if(basecount != 0){ nslog(@"仓库有人正在使用,生产者处于等待"); }else{ //再判断 仓库是否还有可放物品的空间 long maxspacecount = dispatch_semaphore_wait(self.productsemaphore, 5 * nsec_per_sec); if(maxspacecount != 0){ nslog(@"仓库%d个空间已经使用完,生产者处于等待:仓库容量:%lu",_maxproductcount,[self.basearray count]); //生产完了释放临界区的访问锁 dispatch_semaphore_signal(self.criticalsemaphore); }else{ [self.basearray addobject:sp]; nslog(@"新生产一个,仓库目前有:%lu",[self.basearray count]); dispatch_semaphore_signal(self.criticalsemaphore); dispatch_semaphore_signal(self.comsumersemaphore); } } } /** 消费 @return nsstring */ -(nsstring*)xk_comsumer{ nsstring* e = nil; long basecount = dispatch_semaphore_wait(self.criticalsemaphore, 5 * nsec_per_sec); //先获取访问仓库的信号量 if(basecount != 0){ nslog(@"仓库有人正在使用,消费者处于等待"); }else{ //再判断 仓库是否还有可取,如果有物品,则取一个出来,否则t等待 long avablecount = dispatch_semaphore_wait(self.comsumersemaphore, 5 * nsec_per_sec); if(avablecount != 0){ nslog(@"空仓,消费者处于等待"); //生产完了释放临界区的访问锁 dispatch_semaphore_signal(self.criticalsemaphore); }else{ e = [self.basearray objectatindex:[self.basearray count] -1]; [self.basearray removelastobject]; nslog(@"消费了:%@ 仓库还有%lu:",e,[self.basearray count]); //生产完了释放临界区的访问锁 dispatch_semaphore_signal(self.criticalsemaphore); //将仓库中的可放置的数量 +1 dispatch_semaphore_signal(self.productsemaphore); } } return e; } @end
下面测试下这个工作间
xkgraderoom * graderoom = [xkgraderoom new]; //创建一个mydispatchqueue,主要是用于防止资源的竞争,一个线程处使用完资源,然后另外一个才能继续使用 dispatch_queue_t mydispatchqueue = dispatch_queue_create("com.example.gcd,mydispatchqueue", null); dispatch_async(mydispatchqueue, ^{ [graderoom xk_produce:@"queue1"]; nslog(@"queue1-执行完毕"); }); dispatch_async(mydispatchqueue, ^{ [graderoom xk_comsumer]; nslog(@"queue2-执行完毕"); }); dispatch_async(mydispatchqueue, ^{ [graderoom xk_comsumer]; nslog(@"queue3-执行完毕"); }); dispatch_async(mydispatchqueue, ^{ [graderoom xk_produce:@"queue4"]; nslog(@"queue4-执行完毕"); }); dispatch_async(mydispatchqueue, ^{ [graderoom xk_produce:@"queue5"]; nslog(@"queue5-执行完毕"); }); dispatch_async(mydispatchqueue, ^{ [graderoom xk_produce:@"queue6"]; nslog(@"queue6-执行完毕"); }); dispatch_async(mydispatchqueue, ^{ [graderoom xk_comsumer]; [graderoom xk_comsumer]; [graderoom xk_comsumer]; [graderoom xk_comsumer]; nslog(@"queue7-执行完毕"); });
打印结果: