iOS简单优雅的实现复杂情况下的串行需求(各种锁、GCD 、NSOperationQueue...)
昨天一个同事问我一个问题,我在开发中有很多异步操作,回调都需要时间,且时间都不确定,例如一个网络请求,就是这样的形式,异步发起请求,等待回调,等到获取结果之后进行下一步的操作.
我说,没有任何问题啊.本来耗时操作等就是这么写的啊...
然后他说,我现在有一个新的需求,例如网络请求1结束后请求2等到2回来之后再请求3....层层下去...按照顺序来,我说这个需求不算太难.
但是鉴于这个需求很多人都有可能会用到,于是我打算把它给写下来分享给大家
每一次的异步操作大概可以简化成如下:
-(void)doSomeThingForFlag:(NSInteger)flag finish:(void(^)())finshed{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"do:%ld",(long)flag);
sleep(2+arc4random_uniform(4));
NSLog(@"finish:%ld",(long)flag);
if (finshed) {
finshed();
}
});
}
那么正常情况下的写法就是直接按照顺序来
-(void)nomal{
[self doSomeThingForFlag:1 finish:nil];
[self doSomeThingForFlag:2 finish:nil];
[self doSomeThingForFlag:3 finish:nil];
[self doSomeThingForFlag:4 finish:nil];
}
那么这样有效果么?我们运行看看...
很显然,结果没有想象的那么简单,开始请求就已经是无序了...
没有办法,那么使用嵌套?就是最普通的方式看看,
/**
逻辑嵌套
*/
-(void)useNested{
__weak typeof(self)weakSelf = self;
[self doSomeThingForFlag:1 finish:^{
[weakSelf doSomeThingForFlag:2 finish:^{
[weakSelf doSomeThingForFlag:3 finish:^{
[weakSelf doSomeThingForFlag:4 finish:nil];
}];
}];
}];
}
OK ,结果完全按照想要的顺序1->2->3->4
但是这样写会不会就觉得很嵌套的太多了呢?有没有办法不使用这种嵌套来完成这个逻辑呢?
开始构思,首先想到的就是锁,是的,应用开发中有很多所能够完成
iOS作为源自C的更高级语言,自然而然也少不了有各种锁的实现.包含C语言的话,
有OSSpinLock、pthead、@synchronized、NSLock......大概7、8种以上吧..
在不考虑各种锁的性能的情况下,那么是不是所有的都特别适用呢?
我一个一个举例尝试,大致的思路就是创建一个锁,然后通过加锁和解锁的操作来实现串行的需求
首先是使用
pthread_mutex 互斥锁
#import <pthread.h>
/**
pthread_mutex 互斥锁
*/
-(void)usePthred{
static pthread_mutex_t pLock;
pthread_mutex_init(&pLock, NULL);
pthread_mutex_lock(&pLock);
NSLog(@"1上锁");
[self doSomeThingForFlag:1 finish:^{
NSLog(@"1解锁");
pthread_mutex_unlock(&pLock);
}];
pthread_mutex_lock(&pLock);
NSLog(@"2上锁");
[self doSomeThingForFlag:2 finish:^{
NSLog(@"2解锁");
pthread_mutex_unlock(&pLock);
}];
pthread_mutex_lock(&pLock);
NSLog(@"3上锁");
[self doSomeThingForFlag:3 finish:^{
NSLog(@"3解锁");
pthread_mutex_unlock(&pLock);
}];
pthread_mutex_lock(&pLock);
NSLog(@"4上锁");
[self doSomeThingForFlag:4 finish:^{
NSLog(@"4解锁");
pthread_mutex_unlock(&pLock);
}];
}
好吧,轻易的实现了
那么既然互斥锁可以,我再试试另一种pthead
pthread_mutex(recursive) 递归锁
/**
pthread_mutex(recursive) 递归锁
*/
-(void)usePthredResursive{
static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用
static void (^RecursiveBlock)(int);
__weak typeof(self)weakSelf = self;
RecursiveBlock = ^(int value) {
pthread_mutex_lock(&pLock);
if (value>0) {
[weakSelf doSomeThingForFlag:5-value finish:^{
RecursiveBlock(value-1);
}];
}
// if (value == 4) {
// [self doSomeThingForFlag:1 finish:^{
// RecursiveBlock(3);
// }];
// }
// if (value == 3) {
// [self doSomeThingForFlag:2 finish:^{
// RecursiveBlock(2);
// }];
// }
// if (value == 2) {
// [self doSomeThingForFlag:3 finish:^{
// RecursiveBlock(1);
// }];
// }
// if (value == 1) {
// [self doSomeThingForFlag:4 finish:^{
// RecursiveBlock(0);
// }];
// }
pthread_mutex_unlock(&pLock);
};
RecursiveBlock(4);
}
递归锁允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。
结果也是可以能够实现的
表面上看感觉递归锁貌似是没有问题的 但是其实在这里锁并没有起到作用,这里的锁只是锁住了doSomeThingForFlag:finish:
这个方法而已
其实我们把这些全部去掉看看.
-(void)usePthredResursive{
// static pthread_mutex_t pLock;
// pthread_mutexattr_t attr;
// pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
// pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
// pthread_mutex_init(&pLock, &attr);
// pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用
static void (^RecursiveBlock)(int);
__weak typeof(self)weakSelf = self;
RecursiveBlock = ^(int value) {
// pthread_mutex_lock(&pLock);
if (value>0) {
[weakSelf doSomeThingForFlag:5-value finish:^{
RecursiveBlock(value-1);
}];
}
// pthread_mutex_unlock(&pLock);
};
RecursiveBlock(4);
}
结果也是一样?是的,没有想的那么高深,他只是上面嵌套的另一种写法而已,所以递归锁并没有效果,它只是锁住方法本身,保证一次只有一个执行而已,如果我们把block的调用放到方法的外面一样没有作用
-(void)usePthredResursive{
static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用
static void (^RecursiveBlock)(int);
__weak typeof(self)weakSelf = self;
RecursiveBlock = ^(int value) {
pthread_mutex_lock(&pLock);
if (value>0) {
[weakSelf doSomeThingForFlag:5-value finish:nil];
RecursiveBlock(value-1);
}
pthread_mutex_unlock(&pLock);
};
RecursiveBlock(4);
}
这样的话就是没有达到需求
所以我打算放弃递归锁的实现,比如NSRecursiveLock,我直接放弃
联想到这样的方式,我打算再次放弃另外一种锁@synchronized 因为它也只能锁住方法的本身,并控制不了回调的结果
那么就么有方法了么?只能使用递归或者嵌套 或者互斥锁么?
C的方法我又想到了自旋锁
OSSpinLock 自旋锁
#import <libkern/OSAtomic.h>
/**
OSSpinLock 自旋锁
*/
-(void)useOSSpinLock{
__block OSSpinLock oslock = OS_SPINLOCK_INIT;
OSSpinLockLock(&oslock);
NSLog(@"1上锁");
[self doSomeThingForFlag:1 finish:^{
NSLog(@"1解锁");
OSSpinLockUnlock(&oslock);
}];
OSSpinLockLock(&oslock);
NSLog(@"2上锁");
[self doSomeThingForFlag:2 finish:^{
NSLog(@"2解锁");
OSSpinLockUnlock(&oslock);
}];
OSSpinLockLock(&oslock);
NSLog(@"3上锁");
[self doSomeThingForFlag:3 finish:^{
NSLog(@"3解锁");
OSSpinLockUnlock(&oslock);
}];
OSSpinLockLock(&oslock);
NSLog(@"4上锁");
[self doSomeThingForFlag:4 finish:^{
NSLog(@"4解锁");
OSSpinLockUnlock(&oslock);
}];
}
结果终于回到了想要的局面,OSSpinLock没有让我失望.
至此我就想到了,无上面写的方法基本上就是使用各种锁的实现,来达到需求,在结果回调前把线程给锁住,无法继续新的线程,知道该线程的锁解开
那么我们能不能使用多线程的某些方法来实现呢?比如阻塞线程,比如线程的暂停和恢复
首先想到的就是GCD
类似于OSSpinLock, 我们尝试使用GCD的信号量看看能不能够实现
dispatch_semaphore_t
/**
GCD single
*/
-(void)useGCDSingle{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"1阻塞线程");
[self doSomeThingForFlag:1 finish:^{
NSLog(@"1释放线程");
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"2阻塞线程");
[self doSomeThingForFlag:2 finish:^{
NSLog(@"2释放线程");
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"3阻塞线程");
[self doSomeThingForFlag:3 finish:^ {
NSLog(@"3释放线程");
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"4阻塞线程");
[self doSomeThingForFlag:4 finish:^{
NSLog(@"4释放线程");
dispatch_semaphore_signal(semaphore);
}];
}
或者使用
dispatch_suspend、dispatch_resume
这里需要注意一些东西
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
那么想使用这个怎么办呢?
我们可以这样想.因为这几个方法调用来看,大的方式是是串行队列,那么就创建一个串行队列以供暂停和恢复就好了
/**
GCD队列的暂停和恢复
*/
-(void)useGCDSuspendAndResume{
dispatch_queue_t myqueue = dispatch_queue_create("com.charles.queue", NULL);
dispatch_async(myqueue, ^{
dispatch_suspend(myqueue);
[self doSomeThingForFlag:1 finish:^(NSInteger flag) {
dispatch_resume(myqueue);
}];
dispatch_suspend(myqueue);
[self doSomeThingForFlag:2 finish:^(NSInteger flag) {
dispatch_resume(myqueue);
}];
dispatch_suspend(myqueue);
[self doSomeThingForFlag:3 finish:^(NSInteger flag) {
dispatch_resume(myqueue);
}];
dispatch_suspend(myqueue);
[self doSomeThingForFlag:4 finish:^(NSInteger flag) {
dispatch_resume(myqueue);
}];
});
}
无效!我是哪里想错了么?
暂停恢复,想一想,串行队列嘛,当然要串行的添加啦,
/**
GCD队列的暂停和恢复
*/
-(void)useGCDSuspendAndResume{
dispatch_queue_t myqueue = dispatch_queue_create("com.charles.queue", NULL);
dispatch_async(myqueue, ^{
dispatch_suspend(myqueue);
[self doSomeThingForFlag:1 finish:^(NSInteger flag) {
dispatch_resume(myqueue);
}];
});
dispatch_async(myqueue, ^{
dispatch_suspend(myqueue);
[self doSomeThingForFlag:2 finish:^(NSInteger flag) {
dispatch_resume(myqueue);
}];
});
dispatch_async(myqueue, ^{
dispatch_suspend(myqueue);
[self doSomeThingForFlag:3 finish:^(NSInteger flag) {
dispatch_resume(myqueue);
}];
});
dispatch_async(myqueue, ^{
dispatch_suspend(myqueue);
[self doSomeThingForFlag:4 finish:^(NSInteger flag) {
dispatch_resume(myqueue);
}];
});
}
果然,开始是我想错了....
那么既然GCD可以,我使用NSOperationQueue呢?
NSOperationQueue
/**
operationQueue的暂停和恢复
*/
-(void)useOperationQueue{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
__weak typeof(self)weakSelf = self;
NSBlockOperation * operation1 = [NSBlockOperation blockOperationWithBlock:^{
[queue setSuspended:YES];
[weakSelf doSomeThingForFlag:1 finish:^(NSInteger flag) {
[queue setSuspended:NO];
}];
}];
NSBlockOperation * operation2 = [NSBlockOperation blockOperationWithBlock:^{
[queue setSuspended:YES];
[weakSelf doSomeThingForFlag:2 finish:^(NSInteger flag) {
[queue setSuspended:NO];
}];
}];
NSBlockOperation * operation3 = [NSBlockOperation blockOperationWithBlock:^{
[queue setSuspended:YES];
[weakSelf doSomeThingForFlag:3 finish:^(NSInteger flag) {
[queue setSuspended:NO];
}];
}];
NSBlockOperation * operation4 = [NSBlockOperation blockOperationWithBlock:^{
[queue setSuspended:YES];
[weakSelf doSomeThingForFlag:4 finish:^(NSInteger flag) {
[queue setSuspended:NO];
}];
}];
[operation4 addDependency:operation3];
[operation3 addDependency:operation2];
[operation2 addDependency:operation1];
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
}
完全参考GCD的思路,创建每一个操作,添加依赖和最大并发数保证大的串行,同是在没操作其中一个暂停队列,完成后恢复运行....
OK说了这么多,其实就是通过各种方式来实现我最上面提到的需求而已.
这样的操作真的有用么?
很碰巧,我最近在做蓝牙开发,有这样的类似需求,蓝牙发送指令并接收到设备端返回数据的情况就是一次类似的网络请求,
我碰到的需求是按顺序的设置指令到蓝牙设备端,如果是多个UUID或者characteristic的话,之间不冲突,没有影响,但是可惜的是我要操作的是一个characteristic,我只能这么做,因为如果同一时间发送指令不是按照上面的逻辑的话,就会造成丢包.我可能发送了某一个指令,但是蓝牙没有收到或者未处理就来了新的指令导致我无法完整的操作它.我必须保证1->2->3->4的逻辑顺序,
我很高兴我正好在研究这个,所以我能够即时的给到我同事我的思路,,并且今天把它分享给你们
附上demo的地址
https://github.com/spicyShrimp/specialSync.git