iOS 开发中的各种锁

前言:

在多线程编程中,我们经常使用 各种锁 来保证多线程数据 访问时数据的正确性。其中有 atomic,NSLock,Synchronize,dispatch_semaphore_t,NSCondition,NSConditionLock,NSRecursiveLock(递归锁),pthread_mutex,OSSpinLock,dispatch_barrier_async 这几种。我们对比测试这几种在多线程访问中的性能。

项目整体分析:

DEMO 下载地址 : https://github.com/LoveHouLinLi/LockDemo 喜欢的朋友可以star 一波

1.0 基类分析

基类 BaseControl 生成了访问的数组 NSMutableArray *imageNameArray; 方法 - (void)getIamgeNameWithMutiThread;  多线程访问。

 BaseViewController  提供基本的根据Control 初始化的方法。 调用control 的 (void)getIamgeNameWithMutiThread

每个分类 代表一种锁 或者 不加锁 。 根据相应的Item 名称生成对应的 control 。 

2.0 主控器 ViewController 点进去 可以点击不同的item 选项测试不同的锁

EntirelyController 是自动 把所有的锁运行一遍 方便 综合对比。 

运行对比:

1.0 BaseControl  一次点击可能不会出现错误 ,当点击多次 后出现错误

malloc: *** error for object 0x7f917606ac00: incorrect checksum for freed object - object was probably modified after being freed.

*** set a breakpoint in malloc_error_break to debug

说明不加锁的 多线程移除数组操作是 非常危险的 直接造成闪退!

2.0  atomic

/**

新建的一个 atomic 的属性

*/

@property (atomic,strong)NSMutableArray *imageNames;


imageNames  是使用 atomic 修饰的 。 但是 还是出现错误!

Lock_Demo(2234,0x70000378e000) malloc: *** error for object 0x7faeea03d600: pointer being freed was not allocated

*** set a breakpoint in malloc_error_break to debug

2017-12-29 16:09:33.629960+0800 Lock_Demo[2234:130868]                  atomicControl_lock: 0.000139 sec ---- imageNames count:0

Lock_Demo(2234,0x700003894000) malloc: *** error for object 0x7faee9903000: double free

*** set a breakpoint in malloc_error_break to debug


原因是:atomic在set方法里加了锁,防止了多线程一直去写这个property,造成难以预计的数值。但这也只是读写的锁定。跟线程安全其实还是差一些。也就是是赋值property 的时候加锁了。这种情况是 操作这个property 所以并没有用! 

3.0 OneThread 和 MainThread

都是单线程  移除的。可以看出 耗时比较高 。但是没有出现错误  

4.0  NSLock

使用 NSLock 在getImageName 上加锁 。 这种是 互斥锁 。 效率要高很多

// 重写的方法

- (void)getImageName

{

    while (true) {

        NSString *imageName;

        [lock lock];

//        NSLog(@"imageNames count: %ld",imageNameArray.count);

        if (imageNameArray.count > 0) {

            imageName = [imageNameArray firstObject];

            [imageNameArray removeObjectAtIndex:0];

        }else{

            now = CFAbsoluteTimeGetCurrent();

            printf("%30s_lock: %f sec-----imageNames count: %ld\n",[self.title UTF8String] , now-then,imageNameArray.count);

            return;

        }

        [lock unlock];


    }

}

5.0  @synchronized()

@synchronized(self){

      //需要加锁的 代码   保证安全 

}

这个也是类似于互斥锁。  这些都是常用的锁

6.0  dispatch_semaphore_t

dispatch_semaphore_t 是   GCD中信号量,也可以解决资源抢占问题,支持信号通知和信号等待。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。 默认创建的信号量是1 。

首先: dispatch_semaphore_create(1);  此时信号量是1 

然后  在要加锁的代码前  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 此时信号量为 0

最后 在要解锁的时候 dispatch_semaphore_signal(semaphore);  这样别的线程可以访问 实现多线程安全。

7.0   NSCondition

这个测试 相比 NSLock 加锁的方式。  还多了个有趣的测试 这里面 使用到了 多线程之间的通讯。 也就是多线程中 线程A 和 线程 B的  通讯。在NSCondition 中信号量成为我们通讯的方式。 

- (void)newGetImageName:(NSMutableArray *)imageNames

{

    // 这并不是一个 while true 循环

    NSString *imageName;

    [conditionLock lock];

    static int m = 0;

    static int n = 0;

    static int p = 0;

    NSLog(@"removeObjectBegin count: %ld\n",imageNameArray.count);

    if (imageNames.count>0) {

        imageName = [imageNames firstObject];

        [imageNames removeObjectAtIndex:0];

        m++;

        NSLog(@"执行了%d次删除操作",m);

    }else{

        p++;

        NSLog(@"执行了%d次等待",p);

        [conditionLock wait];  //

        imageName = [imageNames firstObject];

        [imageNames removeObjectAtIndex:0];

        n++;

        NSLog(@"执行了%d次继续操作",n);

    }


    NSLog(@"removeObject count: %ld\n",imageNames.count);

    [conditionLock unlock];    //解锁

}


不论imageNames.count 是正还是负 都执行 删除操作  而没有立即崩溃 !!!

这是因为

NSCondition提供更高级的用法。wait和signal,和条件信号量类似。

比如我们要监听imageNames数组的个数,当imageNames的个数大于0的时候就执行清空操作。思路是这样的,当imageNames个数大于0时执行清空操作,否则,wait等待执行清空操作。当imageNames个数增加的时候发生signal信号,让等待的线程唤醒继续执行。

NSCondition和NSLock、@synchronized等是不同的是,NSCondition可以给每个线程分别加锁,加锁后不影响其他线程进入临界区。这是非常强大。

但是正是因为这种分别加锁的方式,NSCondition使用wait并使用加锁后并不能真正的解决资源的竞争。比如我们有个需求:不能让m<0。假设当前m=0,线程A要判断到m>0为假,执行等待;线程B执行了m=1操作,并唤醒线程A执行m-1操作的同时线程C判断到m>0,因为他们在不同的线程锁里面,同样判断为真也执行了m-1,这个时候线程A和线程C都会执行m-1,但是m=1,结果就会造成m=-1.

当我用数组做删除试验时,做增删操作并不是每次都会出现,大概3-4次后会出现。单纯的使用lock、unlock是没有问题的。

于是变成了 添加一个 删除一个 每个线程加锁 。 有一点 我们需要注意  [conditionLock broadcast];  在 

API 解析: 

- (void)wait;//挂起线程

- (BOOL)waitUntilDate:(NSDate *)limit;

- (void)signal; //任意通知一个线程

- (void)broadcast; //通知所有等待的线程

类似GCD的信号量,wait之后当前线程会被阻塞直到 lock signal。

在用的时候注意,首先对lock对象进行lock



8.0  NSConditionLock

除了 正常的加锁 和 解锁的 用法  如下 所示

同样除了     重写的方法

- (void)getImageName

{

    while (true) {

        NSString *imageName;

        [lock lock];

        NSLog(@"imageNames count: %ld",imageNameArray.count);

        if (imageNameArray.count > 0) {

            imageName = [imageNameArray firstObject];

            [imageNameArray removeObjectAtIndex:0];

        }else{

            now = CFAbsoluteTimeGetCurrent();

            printf("%30s_lock: %f sec-----imageNames count: %ld\n",[self.title UTF8String] , now-then,imageNameArray.count);

            return;

        }

        [lock unlock];

    }

}

我们可以使用 NSConditionLock 来做一些  多线程交互的操作  

在 - (void)createImageName:(NSMutableArray *)imageNames 方法中  我们使用 [lock lockWhenCondition:0]; 

- (void)createImageName:(NSMutableArray *)imageNames

{

    [lock lockWhenCondition:0];  // 

    static int m = 0;  // m

    [imageNames addObject:@"0"];

    m++;

    NSLog(@"添加了%d次",m);

    NSLog(@"createImageName count: %ld\n",imageNames.count);

    [lock unlockWithCondition:1];

}

// 删除操作  

- (void)getImageName:(NSMutableArray *)imageNames

{

    NSString *imageName;

    // NSConditionLock 的

    [lock lockWhenCondition:1];

    static int m = 0;  //

    NSLog(@"removeObjectBegin count: %ld\n",imageNames.count);

    if (imageNames.count > 0) {

        imageName = [imageNames firstObject];

        [imageNames removeObjectAtIndex:0];

        m++;

        NSLog(@"执行了%d次删除操作",m);

    }

    NSLog(@"removeObject count: %ld\n",imageNames.count);


//    [lock unlockWithCondition:1];  //

    [lock unlockWithCondition:0];  // 解锁 0 的方法  也就是create 的方法 


}


整体 多线程 调用的操作 

- (void)getIamgeNameWithMutiThread

{

    NSMutableArray *imageNames = [NSMutableArray array];

    dispatch_group_t dispatchGroup = dispatch_group_create();

    __block double then,now;

    then = CFAbsoluteTimeGetCurrent();  // 获取绝对时间

    for (int i = 0; i<1024; i++) {

        dispatch_group_async(dispatchGroup, self.syncronizationQueue, ^{

            [self getImageName:imageNames];

        });


        dispatch_group_async(dispatchGroup, self.syncronizationQueue, ^{

            [self createImageName:imageNames];

        });

    }


    //

    dispatch_group_notify(dispatchGroup, self.syncronizationQueue, ^{

        now = CFAbsoluteTimeGetCurrent();

        NSLog(@"%30s_lock: %f sec-----imageNames count: %ld\n",[self.title UTF8String] , now-then,imageNames.count);

    });

}


注意 [lock lockWhenCondition:0]; 0 的方法的优先级别 比 1 要高  [lock lockWhenCondition:1]; 1  在 dispatch_group_notify 中调用的方法先调用 0 的方法 后面是1 的方法  改变

于是变成了 创建一张图片 删除 一张图片 但是是多线程操作的 也是线程安全的操作。

9.0  NSRecursiveLock

故名思意 这是 递归锁 。 递归锁 用于递归调用的方法里面。 因为递归调用 加互斥锁 会造成 死锁。 

// 获取 imageNames

- (void)getImageName:(NSMutableArray *)imageNames

{

    NSString *imageName;

    [lock lock];

    if (imageNames.count > 0) {

        imageName = [imageNames firstObject];

        [imageNames removeObjectAtIndex:0];

        // 循环删除  这点和

        [self getImageName:imageNames];

    }

    [lock unlock];

}

10.0   pthread_mutex_lock

这也是互斥锁 使用方法

pthread_mutex_t mutex; 

  第一步 创建  pthread_mutex_init(&mutex, NULL);

第二步  加锁   pthread_mutex_lock(&mutex);

第三步 解锁   pthread_mutex_unlock(&mutex);

第四步 释放锁 这点 和 dispatch_semaphore_t  不一样  这是要我们注意的地方 

- (void)dealloc

{

    // 销毁 需要手动 销毁 不然有内存泄漏

    pthread_mutex_destroy(&mutex);

}

11.0   OSSpinLock

OSSpinLock spinLock;

第一步 初始化锁 spinLock = OS_SPINLOCK_INIT; // 初始化

第二步 加锁  OSSpinLockLock(&spinLock);

第三步 解锁  OSSpinLockUnlock(&spinLock);

OSSpinLock 自旋锁,性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务。对于内存缓存的存取来说,它非常合适。

可以在 Xcode IDE 中看出  在iOS 10.0 上已经被 deprecated 了。

12.0  dispatch_barrier_async/dispatch_barrier_sync

dispatch_barrier_async/dispatch_barrier_sync在一定的基础上也可以做线程同步,会在线程队列中打断其他线程执行当前任务,也就是说只有用在并发的线程队列中才会有效,因为串行队列本来就是一个一个的执行的,你打断执行一个和插入一个是一样的效果。两个的区别是是否等待任务执行完成。

@property (nonatomic,readonly)dispatch_queue_t syncronizationQueue;

dispatch_barrier_async(self.syncronizationQueue, ^{

            [self getImageName];

        });

参考博客:

//www.greatytc.com/p/35dd92bcfe8c















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

推荐阅读更多精彩内容