iOS 开发中加锁

[1].OSSpinLock 自旋锁

//导入头文件
#import<libkern/OSAtomic.h>

  /*
        //dispatch_get_global_queue 系统提供的一个并发队列
                        同步                  异步
        串行队列    当前线程,一个一个执行     其他线程,一个一个执行
        并行队列    当前线程,一个一个执行     开很多线程,一起执行
  */
//线程1
  
        __block OSSpinLock osLock = OS_SPINLOCK_INIT;dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 准备上锁");
    
        OSSpinLockLock(&oslock);
        sleep(4);
        NSLog(@"线程1");
        
       OSSpinLockUnlock(&oslock);
        NSLog(@"线程1 解锁成功");
        
        NSLog(@"-------------------------------");
    });

//线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 准备上锁");
        
        OSSpinLockLock(&oslock);
        NSLog(@"线程2");
        
        OSSpinLockUnlock(&oslock);
        NSLog(@"线程2 解锁成功");
        
        NSLog(@"################################");
    });

屏幕快照 2017-01-11 下午1.56.41.png

[1]自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。由于自旋锁使用者一般保持锁 时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
[2]图中可以发现:当我们锁住线程1时,在同时锁住线程2的情况下,线程2会一直等待(自旋锁不会让等待的进入睡眠状态,直到线程1的任务执行完且解锁完毕,线程2会立即执行;正常情况下,lock 和unlock最好成对出现。
[3]但是因为自旋锁存在优先级反转问题,简单说如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。所以苹果弄了os_unfair_lock来代替自旋锁但是 只有IOS10 中才有~~~

[2]. dispatch_semaphore 信号量


//传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句
    dispatch_semaphore_t signal = dispatch_semaphore_create(1);
    
    //设置timeOut NSEC_PER_SEC表示在当前时间延时3秒
    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
    
    /*
        
     //常用的三个函数
     dispatch_semaphore_create(1): 传入值必须 >=0, 若传入为 0 则阻塞线程并等待timeout,时间到后会执行其后的语句
     dispatch_semaphore_wait(signal, overTime):可以理解为 lock,会使得 signal 值 -1
     dispatch_semaphore_signal(signal):可以理解为 unlock,会使得 signal 值 +1
     */
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 等待ing");
        dispatch_semaphore_wait(signal, overTime); //signal 值 -1
        
        NSLog(@"线程1");
        dispatch_semaphore_signal(signal); //signal 值 +1
        
        NSLog(@"线程1 发送信号");
        NSLog(@"--------------------------------------------");
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 等待ing");
        dispatch_semaphore_wait(signal, overTime);
        
        NSLog(@"线程2");
        dispatch_semaphore_signal(signal);
        
        NSLog(@"线程2 发送信号");
    });
屏幕快照 2017-01-11 下午2.37.22.png

关于信号量,我们可以用停车来比喻:
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。信号量的值(signal)就相当于剩余车位的数目,dispatch_semaphore_wait
函数就相当于来了一辆车,dispatch_semaphore_signal
就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait 剩余车位就减少一个;当剩余车位为 0 时,再来车(即调用 dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。

[3]. pthread_mutex 互斥锁

/*
        pthread_mutex_init()函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。
        如果参数attr为NULL,则使用默认的互斥锁属性,默认属性为快速互斥锁 。以下是互斥锁的属性
        
        * PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
     
       * PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
     
       * PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
     
       * PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
          
          pthread_mutex_init(&lock,NULL);初始化锁
          pthread_mutex_lock(&lock);上锁
          //do your stuff
          pthread_mutex_unlock(&lock);解锁
          pthread_mutex_destroy(&lock);销毁锁
     */
    
    
    static pthread_mutex_t pLock;
    
    pthread_mutex_init(&pLock, NULL);
    
    //1.线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 准备上锁");
        pthread_mutex_lock(&pLock);
        sleep(3);
        NSLog(@"线程1");
        pthread_mutex_unlock(&pLock);
        NSLog(@"------------------");
    });
    
    //1.线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 准备上锁");
        pthread_mutex_lock(&pLock);
        NSLog(@"线程2");
        pthread_mutex_unlock(&pLock);
        NSLog(@"##################  $");
    });

屏幕快照 2017-01-11 下午2.58.09.png

使用互斥锁时要注意死锁,死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西。
  总体来讲, 有几个不成文的基本原则:
  对共享资源操作前一定要获得锁。
  完成操作以后一定要释放锁。
  尽量短时间地占用锁。
  如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
  线程错误返回时应该释放它所获得的锁。

[4].NSLock 普通锁

//NSLock 普通锁
    /*
        lock、unlock:不多做解释,和上面一样
        trylock:能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO
        lockBeforeDate:这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO
     */
    
    
    NSLock *lock = [NSLock new];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 尝试加锁ing...");
        [lock lock];
        sleep(5);//睡眠5秒
        NSLog(@"线程1");
        [lock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 尝试加锁ing...");
        BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
        if (x) {
            NSLog(@"线程2");
            [lock unlock];
        }else{
            NSLog(@"失败");
        }
    });
屏幕快照 2017-01-11 下午3.38.09.png

[5]. NSCondition

//NSCondition
    /*
        wait:进入等待状态
        waitUntilDate::让一个线程等待一定的时间
        signal:唤醒一个等待的线程
        broadcast:唤醒所有等待的线程
     */
    
    
    NSCondition *cLock = [NSCondition new];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lock];
        NSLog(@"线程1加锁成功");
        [cLock wait];
        NSLog(@"线程1");
        [cLock unlock];
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lock];
        NSLog(@"线程2加锁成功");
        [cLock wait];
        NSLog(@"线程2");
        [cLock unlock];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        NSLog(@"唤醒一个等待的线程");
        [cLock signal];
    });
屏幕快照 2017-01-11 下午3.53.02.png

[6].@synchronized 条件锁

//线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized (self) {
            sleep(2);
            NSLog(@"线程1");
        }
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized (self) {
            NSLog(@"线程2");
        }
    });
屏幕快照 2017-01-11 下午4.00.37.png

[7].NSConditionLock 条件锁

//NSConditionLock
    /*
        相比于 NSLock 多了个 condition 参数,我们可以理解为一个条件标示。

    
    */
    //线程1
    
    NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if([cLock tryLockWhenCondition:0]){
            NSLog(@"线程1");
            [cLock unlockWithCondition:1];
        }else{
            NSLog(@"失败");
        }
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:3];
        NSLog(@"线程2");
        [cLock unlockWithCondition:2];
    });
    
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:1];
        NSLog(@"线程3");
        [cLock unlockWithCondition:3];
    });
屏幕快照 2017-01-11 下午4.04.48.png

我们在初始化 NSConditionLock 对象时,给了他的标示为 0
执行 tryLockWhenCondition:时,我们传入的条件标示也是 0,所 以线程1 加锁成功
执行 unlockWithCondition:时,这时候会把condition由 0 修改为 1
因为condition 修改为了 1, 会先走到 线程3,然后 线程3 又将 condition 修改为 3
最后 走了 线程2 的流程
从上面的结果我们可以发现,NSConditionLock 还可以实现任务之间的依赖。

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

推荐阅读更多精彩内容

  • 锁是一种同步机制,用于多线程环境中对资源访问的限制iOS中常见锁的性能对比图(摘自:ibireme): iOS锁的...
    LiLS阅读 1,505评论 0 6
  • 转载自:http://www.cocoachina.com/ios/20161129/18216.html 递归锁...
    时米高的人生笔记阅读 301评论 3 2
  • 版本记录 前言 ios中有好几种锁,比如自旋锁,互斥锁,信号量等等,锁其实是多线程数据安全的一种解决方案,作用就是...
    刀客传奇阅读 1,372评论 0 4
  • 在日常开发过程中,为了提升程序运行效率,以及用户体验,我们经常使用多线程。在使用多线程的过程中,难免会遇到资源竞争...
    星捷阅读 834评论 0 0
  • 01 外甥女并非天资聪明型,再加上幼儿园没有认真上,玩着过来的,所以,一至三年级时,学习特别吃力。 三年级时,数学...
    梅十九阅读 216评论 0 0