iOS简单优雅的实现复杂情况下的串行需求(各种锁、GCD 、NSOperationQueue...)

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 原文地址 http://www.cnblogs.com/kenshincui/p/3983982.html 大家都...
    怎样m阅读 1,263评论 0 1
  • 多线程 在iOS开发中为提高程序的运行效率会将比较耗时的操作放在子线程中执行,iOS系统进程默认启动一个主线程,用...
    郭豪豪阅读 2,586评论 0 4
  • 概览 1.多线程 1.1 简介 1.2 iOS 多线程 2.NSThread 2.1 解决多线程阻塞问题 2.2 ...
    Yiart阅读 2,133评论 0 8
  • 从哪说起呢? 单纯讲多线程编程真的不知道从哪下嘴。。 不如我直接引用一个最简单的问题,以这个作为切入点好了 在ma...
    Mr_Baymax阅读 2,726评论 1 17
  • 我突然想起, 你已沉睡在六月的寒冬里, 穆肃庄重, 脸颊上没有一丝微笑, 也没有一丝悲伤。 仿佛我能够吻你, 把你...
    希尔大阅读 232评论 0 3