013-iOS锁机制

锁的类别

  • NSLocking
    • NSLock
    • NSConditionLock 条件锁
    • NSRecursiveLock 递归锁
    • NSCondition
    • NSDistributedLock 分布锁
  • @sychronized
  • dispatch_semaphore
  • OSSpinLock 自旋锁
  • pthread_mutex

首先我们要实现一种简单的线程访问导致的数据混乱的情况

- (void)threadTestLearn
{
    __block NSInteger flag = 0;
    
    void (^positive)() = ^{
        while (1) {
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}

这个方法的打印结果,预期应该是

1 == 1
-1 == -1
1 == 1

而事实上,由于没有互斥访问机制,打印结果出现了

1 == -1
-1 == 1

NSLocking

诸如 NSLock、NSConditionLock 等锁都继承了 NSLocking 协议,NSLocking 协议简单粗暴地定义了两个方法

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

一个是加锁,一个是解锁。

NSLock

同一个 NSLock 对象显式调用 lock 函数后,除非自己显示调用 unlock 函数,否则其他线程均不能继续对此 NSLock 对象加锁,从而达到互斥访问的目的。 所以使用也很简单

- (void)threadTestLearn
{
    __block NSInteger flag = 0;
    NSLock *lock = [NSLock new];
    
    void (^positive)() = ^{
        while (1) {
            [lock lock];
            
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            
            [lock unlock];
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            [lock lock];
            
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            
            [lock unlock];
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}

如果希望获取锁时不阻塞线程,则考虑使用 tryLock 方法,它会在获取锁失败后返回 NO,可以继续执行后面的代码。

lockBeforeDate 方法在指定的时间以前得到锁。YES: 在指定时间之前获得了锁;NO: 在指定时间之前没有获得锁。该线程将被阻塞,直到获得了锁,或者指定时间过期。

NSLockConditionLock

NSConditionLock 是一种条件锁,除了基本的lock与unlock函数,还提供了 lockWithCondition 以及 unlockWithCondition 方法,适合多个线程的工作需要按照顺序执行的情景。其中 unLockWithCondition 会把 condition 变量修改为指定参数后解锁。

- (void)threadTestLearn
{
    __block NSInteger flag = 0;
    __block NSInteger mutext = 1;
    
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:mutext];
    
    void (^positive)() = ^{
        while (1) {
            [lock lockWhenCondition:1];
            
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            
            [lock unlockWithCondition:-1];
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            [lock lockWhenCondition:-1];
            
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            
            [lock unlockWithCondition:1];
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}

这里通过 mutex 变量严格保证了 positive 和 negative 方法会依次调用,并且第一个调用的方法一定是 positive 方法,同时如果将 negative 中的语句改成

            [lock unlockWithCondition:0];

则两个方法都只会被调用一次就停止了,因为都获得不了正确的 condition 所以无法获得锁。

NSRecursiveLock

NSRecursiveLock 是递归锁,用于解决递归调用中的死锁问题。当一个方法加锁以后递归调用自己,会再次进行加锁,由于锁没有被释放所以线程会被阻塞掉,线程阻塞后将永远不能解锁,因此发生死锁。

NSRecursiveLock 实际上定义的是一个递归锁,主要是用在循环或递归操作中。它可以允许同一线程多次加锁,而不会造成死锁。递归锁会跟踪它被 lock 的次数。每次成功的 lock 都必须平衡调用 unlock 操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。

    NSRecursiveLock *recursiveLock = [NSRecursiveLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        static void (^test)(int);
        test = ^(int value)
        {
            [recursiveLock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"%d", value);
                test(--value);
            }
            else
            {
                [recursiveLock unlock];
                return;
            }
            NSLog(@"%d", value);
            [recursiveLock unlock];
        };
        __block int value = 5;
        test(value);
    });

NSCondition

NSCondition 与 NSConditionLock 是两回事,它们之间最大的区别在于 NSCondition 支持在获得锁之后阻塞线程并放弃锁,然后等待被唤醒后可以重新获得锁。
这里被唤醒有两种方式,signal 和 broadcast,前者只会唤醒一个 wait 的线程,而后者会唤醒所有 wait 的线程。

但是有一个问题:在多个线程 wait 的场景下,一个线程调用了 broadcast 后如何唤醒多个 wait 的线程,能否保证线程安全,以及这个线程在未 unlock 前执行操作如何保证线程安全。

- (void)threadTestLearn
{
    __block NSInteger flag = 0;
    __block NSInteger mutex = 1;
    
    NSCondition *condition = [NSCondition new];
    
    void (^positive)() = ^{
        while (1) {
            [condition lock];
            NSLog(@"thread1");
            while (mutex == 0)
            {
                [condition wait];
            }
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            
            mutex = 0;
            [condition signal];
            [condition unlock];
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            [condition lock];
            NSLog(@"thread-1");
            if (mutex == 0)
            {
                [condition wait];
            }
            
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            
            mutex = 0;
            [condition signal];
            [condition unlock];
        }
    };
    
    void (^zero)() = ^{
        while (1) {
            [condition lock];
            
            if (mutex != 0)
            {
                [condition wait];
            }
            NSLog(@"mutex 0");
            [NSThread sleepForTimeInterval:1];
            
            mutex = 1;
            [condition broadcast];
            [condition unlock];
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), zero);
}

@synchronized

@synchronized 可以保证唯一标识下的资源互斥访问,不需要显式创建锁对象,使用方便,但是表现性能不佳。

    __block NSInteger flag = 0;
    
    void (^positive)() = ^{
        while (1) {
            @synchronized (@(flag)) {
                [NSThread sleepForTimeInterval:1];
                flag = 1;
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"1 == %ld", flag);
                
            }
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            @synchronized (@(flag)) {
                [NSThread sleepForTimeInterval:1];
                flag = -1;
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"-1 == %ld", flag);
            }
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);

dispatch_semaphore

dispatch_semaphore 是 GCD 中提供的信号量机制,通过信号量控制互斥访问,提供了 wait 和 signal 方法,类似于信号量的 PV 操作。出于性能考虑建议开发中使用这个作为锁。

  • wait 对信号量减一,如小于 0 则等待
  • signal 如果没有等待接受信号的线程,则对信号量加一
    __block NSInteger flag = 0;
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    
    void (^positive)() = ^{
        while (1) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            dispatch_semaphore_signal(semaphore);
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            dispatch_semaphore_signal(semaphore);
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);

OSSpinLock

自旋锁,当无法获得锁时会一直空循环,占用 CPU,性能最高,适合执行轻量级工作,但是有隐藏的线程安全问题。

使用时需要引入包 #import <libkern/OSAtomic.h>。

    __block NSInteger flag = 0;
    __block OSSpinLock lock = OS_SPINLOCK_INIT;
    
    void (^positive)() = ^{
        while (1) {
            OSSpinLockLock(&lock);
            
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            
            OSSpinLockUnlock(&lock);
            [NSThread sleepForTimeInterval:1];
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            OSSpinLockLock(&lock);
            
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            
            OSSpinLockUnlock(&lock);
            [NSThread sleepForTimeInterval:1];
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);

pthread_mutex

这是 C 语言中定义的互斥锁,对于未获得锁的线程不使用忙等,而是直接睡眠。

首先引入头文件 #import <pthread.h>

然后初始化 lock 变量

    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
    __block pthread_mutex_t lock;
    pthread_mutex_init(&lock, &attr);
    pthread_mutexattr_destroy(&attr);

这里 attr 有几种 type

PTHREAD_MUTEX_NORMAL 缺省类型,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。

PTHREAD_MUTEX_ERRORCHECK 检错锁,如果同一个线程请求同一个锁,则返回 EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。

PTHREAD_MUTEX_RECURSIVE 递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。

PTHREAD_MUTEX_DEFAULT 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。

加锁解锁过程类似其他锁

pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);

pthread_mutex_destroy 为释放锁资源。

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

推荐阅读更多精彩内容