iOS多线程 ---线程安全

背景:在多线程中,如果同时抢一块资源,比如给数组追加数据,可变数组(字典)不是线程安全的,就会导致crash,为了避免这种问题,就衍生出线程同步,来保证线程安全。

先介绍几种锁,

第一种锁

@synchronized(互斥锁)

NSObject *obj = [[NSObject alloc] init];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

      @synchronized(obj) {

             NSLog(@"需要线程同步的操作1 开始");

             sleep(3);

             NSLog(@"需要线程同步的操作1 结束");

      }

});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

           sleep(1);

           @synchronized(obj) {

                  NSLog(@"需要线程同步的操作2");

         }

});

1.@synchronized(obj)指令使用的obj为该锁的唯一标识,@synchronized(obj)指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程2中的@synchronized(obj)改为@synchronized(self),刚线程2就不会被阻塞,@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。

2.synchronized 优劣分析:

劣势:@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。开销大,性能差,不适合在生成环境使用。

优点:使用@synchronized关键字可以很方便地创建锁对象,而且不用显式的创建锁对象。

3.synchronized 内部的锁是一个递归锁。

4.atomic:原子属性,为setter方法加锁,的内部实现就是synchronized。

第二种锁

NSLock(性质:互斥锁)

容易造成死锁,比如:

//主线程中

NSLock *theLock = [[NSLock alloc] init];

TestObj *obj = [[TestObj alloc] init];

//线程1

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        static void(^TestMethod)(int);

        TestMethod = ^(int value){

               [theLock lock];

               if (value > 0){

                      [obj method1];

                     sleep(5); //后面写上[theLock unlock];而不是放在最后,就加锁,解锁就一一对应了,

                    TestMethod(value-1);

             }

        [theLock unlock];

      };

        TestMethod(5);

});

//线程2

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

         sleep(1);

        [theLock lock];

         [obj method2];

        [theLock unlock];

});

//简单分析:这段代码是一个典型的死锁情况。在我们的线程中1是递归调用的。所以每次进入这个block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,

//调试器有这些提示:[NSLock lock]: deadlock

//如果在在递归或循环中正确的使用锁:

NSRecursiveLock 递归锁,直接NSLock *theLock = [[NSLock alloc] init];

替换成NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];就ok了

第三种锁

NSConditionLock条件锁

特殊场景使用,也就是满足一定的条件下,才解锁,和创建锁。

第四种方式,用信号量来实现锁的功能

信号量 ==>当信号个数为 0 时,则线程阻塞,等待发送新信号;一旦信号个数大于 0 时,就开始处理任务。

dispatch_semaphore_create、

dispatch_semaphore_wait  //在执行完这行代码后,信号量就会减一,如果信号量变成了0,那么就在这里等着,后面一般就是一些排他操作(比如:数组的写操作,保证同步)

dispatch_semaphore_signal、 //作用:在排他操作结束后,现在就可以让其他线程来玩了,执行完这行代码,信号量加1,之前其他等待的线程就又开始工作了。

dispatch_semaphore_t signal = dispatch_semaphore_create(1);

dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

         dispatch_semaphore_wait(signal, overTime);

         NSLog(@"需要线程同步的操作1 开始");

          sleep(2); //数组写操作

          NSLog(@"需要线程同步的操作1 结束");

         dispatch_semaphore_signal(signal);

});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

          sleep(1);

         dispatch_semaphore_wait(signal, overTime);

          NSLog(@"需要线程同步的操作2");

          dispatch_semaphore_signal(signal);

});

//结果就是:

2016-06-29 20:47:52.324 SafeMultiThread[35945:579032] 需要线程同步的操作1 开始

2016-06-29 20:47:55.325 SafeMultiThread[35945:579032] 需要线程同步的操作1 结束

2016-06-29 20:47:55.326 SafeMultiThread[35945:579033] 需要线程同步的操作2

//这里设置的 dispatch_semaphore_wait(signal, overTime);有个超时时间,一般可以写作DISPATCH_TIME_FOREVER,永不超时。

//这里overtime 如果改成1*NSEC_PER_SEC,那么wait就不等了,就向后走了,

2016-06-30 18:53:24.049 SafeMultiThread[30834:434334] 需要线程同步的操作1 开始

2016-06-30 18:53:25.554 SafeMultiThread[30834:434332] 需要线程同步的操作2

2016-06-30 18:53:26.054 SafeMultiThread[30834:434334] 需要线程同步的操作1 结束

简单总结一下:

互斥锁特点:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。

自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。性能最高的锁,OSSpinLock已经不再安全,不再使用。

信号量:一个线程完成了操作完共享数据后,就通过发出信号量告诉别的线程,你们现在可以用这块共享资源了。

性能比较:OSSpinLock > dispatch_semaphore > NSLock > NSRecursiveLock > NSConditionLock > @synchronized.

生产环境一般推荐使用dispatch_semaphore。

上述都是解决同一时刻,只允许一个线程访问某个特定资源的问题,不管是是互斥锁还是信号量,都没法解决另外一个问题:锁定的共享资源会引起读写问题,大多数情况下,限制资源一次只能有一个线程进行读取访问其实是非常浪费的。所以就引出了GCD 的barrier来解决这个问题(资源饥饿)。

GCD barrier 解决资源饥饿 问题


dispatch_barrier使用前提:一定要在一个队列里面(自定义并行队列)。

不能用global queue,因为这个全局队列,每次系统分配可能在不同的队列里面,比如你的barrier在队列A里面,流程走到这,执行自己barrier任务,没用,后面的任务在其他队列做了,不等barrier任务,所以barrier根本不起效果。

剩下就是自定义一个队列,分串行队列,和并行队列,如果是串行队列,就不用了barrier了,他本身就是按照顺序来执行的,加barrier拦截没有意义,所以只能用自定义队列的并行方式。

concurrentQueue =dispatch_queue_create("www.test.com", DISPATCH_QUEUE_CONCURRENT);

- (NSString *)someString{

       __weak NSString *localSomeString; //1

       dispatch_sync(concurrentQueue, ^{

              localSomeString = _someString; //2

        });

      return localSomeString; //3

}

- (void)setSomeString:(NSString *)someString{

       // barrier

       dispatch_barrier_async(concurrentQueue, ^{

             _someString = someString;

       });

}

//当使用并发队列时,要确保所有的 barrier 调用都是 async 的,如果你使用 dispatch_barrier_sync ,那么你很可能会使你自己(更确切的说是,你的代码)产生死锁。

分析:写操作 ,只能用barrier,保证多线程写的安全,读取操作,可以是并行,

dispatch_sync(concurrentQueue, ^{localSomeString = _someString;});

return localSomeString;

这个操作是把block内容放到并行队列里面,这里用dispatch_sync原因就是后面还需要    return localSomeString;也就是必须让他等着,不能block里面赋值还没做,就直接返回了,所以要用dispatch_sync,流程就成了,下面的1,2,3符合逻辑。这里虽然是同步执行,但是任务是放在并行队列里面的,所以任务还是并行执行的。当然,如果这里不是读取操作,是其他操作,不需要后面的返回值,就可以用dispatch_async,异步执行,所以需要看你具体做的事情。


其他:

并行: 自定义并行队列  、 全局队列  ==>放在里面的任务block ,是可以同步执行的

串行: 自定义串行队列  、  主线程  ==> 放在里面的任务block,是一个挨着一个,按照顺序来执行的。

同步:dispatch_sync   ==>  就在当前线程做事情

异步:dispatch_async  ==>  会开一个新线程 做事情

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

推荐阅读更多精彩内容