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

    __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

然后初始化 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 为释放锁资源。

你可能感兴趣的:(013-iOS锁机制)