OC总结 - 锁

841 阅读6分钟

什么是线程安全?

多线程操作共享数据的时候不会出现意想不到的结果就叫线程安全,否则就是线程不安全。

原子属性是线程安全的吗?

原子属性只能保障set 或者 get的读写安全,但我们在使用属性的时候,往往既有set又有get,所以 说原子属性并不是线程安全的。

自旋锁 & 互斥锁

自旋锁: 在访问被锁的资源的时候,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释 放锁。(忙等)

互斥锁: 在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁的资 源释放锁。然后再唤醒休眠线程。(闲等)

自旋锁的优点在于,因为自旋锁不会引起调用者线程休眠,所以不会进行线程调度,cpu时间片轮 转等一些耗时的操作。所以如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁。

自旋锁缺点在于,自旋锁一直占用CPU,在未获得锁的情况下,一直自旋,相当于死循环,会一直 占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。 而且自旋锁不能实现递归调用 。

自旋锁优先级反转的bug

当多个线程有优先级的时候,如果一个优先级低的线程先去访问某个数据,此时使用自旋锁进行了 加锁,然后一个优先级高的线程又去访问这个数据,那么优先级高的线程因为优先级高会一直占着 CPU资源,此时优先级低的线程无法与优先级高的线程争夺 CPU 时间,从而导致任务迟迟完不 成、锁无法释放。

由于自旋锁本身存在的这个问题,所以苹果在 iOS 10 以后已经废弃了 OSSpinLock。 也就是说除非大家能保证访问锁的线程全部都处于同一优先级,否则 iOS 系统中的自旋锁就不要去使用了.

OSSpinLock

自旋锁

iOS 10 后弃用, 之后使用 os_unfair_lock

// 创建
OSSpinLock lock = OS_SPINLOCK_INIT; 
// 加锁
OSSpinLockLock(&lock); 
// 尝试加锁,返回 bool 值表示,true表示加锁成功,false表示加锁失败,锁正在被其他线程持有
BOOL s = OSSpinLockTry(&lock); 
// 解锁
OSSpinLockUnlock(&lock);

os_unfair_lock

互斥锁

作为 OSSpinLock 弃用后的替代出现

使用时需要导入头文件 #import <os/lock.h>

// 创建
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// 加锁
os_unfair_lock_lock(&lock);
// 尝试加锁,返回 bool 值表示,true表示加锁成功,false表示加锁失败,锁正在被其他线程持有
BOOL s = os_unfair_lock_trylock(&lock);
// 解锁
os_unfair_lock_unlock(&lock);
// 如果当前线程为持有指定的锁或者锁已被解锁,则触发崩溃
os_unfair_lock_assert_owner(&lock);
// 如果当前线程持有指定的锁,则触发崩溃
os_unfair_lock_assert_not_owner(&lock);

NSLock

互斥锁

非递归锁,不能重复加锁,否则会死锁

// 创建
NSLock *lock = [NSLock new];
// 加锁
[lock lock];
// 解锁
[lock unlock];
// 尝试加锁
BOOL s = [lock tryLock];

NSCondition 条件锁

互斥锁

非递归锁,不能重复加锁,否则会死锁

当线程之间存在数据相互依赖的情况下,它能起到很好的协调作用。

// 创建
NSCondition *lock = [NSCondition new];
// 加锁
[lock lock];
// 等待。阻塞当前线程,使线程进入休眠,等待唤醒信号
[lock wait];
// 唤醒。唤醒一个正在休眠的线程,如果要唤醒多个,需要调用多次。如果没有线程在等待,则什么也不做
[lock signal];
// 唤醒所有在等待的线程。如果没有线程在等待,则什么也不做
[lock broadcast];
// 解锁
[lock unlock];

虚假唤醒

当线程从等待已发出信号的条件变量中醒来,却发现它等待的条件不满足时,就会发生虚假唤醒。 之所以称为虚假,是因为该线程似乎无缘无故地被唤醒了。但是虚假唤醒不会无缘无故发生:它们 通常是因为在发出条件变量信号和等待线程最终运行之间,另一个线程运行并更改了条件。

在许多系统上,尤其是多处理器系统上,虚假唤醒的问题更加严重,因为如果有多个线程在条件变 量发出信号时等待它,系统可能会决定将它们全部唤醒,将每个 signal() 唤醒一个线程视为 broadcast() 唤醒所有,从而打破了信号和唤醒之间任何可能预期的 1:1 关系。如果有 10 个线 程在等待,那么只有一个会获胜,另外 9 个会经历虚假唤醒。

使用示例

for (int i = 0; i < 100; i ++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.iCondition lock];
        self.count ++;
        NSLog(@"生产了一个产品,现有产品 : %d个",self.count);
        [self.iCondition signal];
        [self.iCondition unlock];
    });
}

for (int i = 0; i < 100; i ++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.iCondition lock];
        // 使用 while 解决 虚假唤醒 问题
        // 如果产品 为 0 ,无法消费,将一直等待产品生产出来
        while (self.count == 0) {
            [self.iCondition wait];
        }
        self.count --;
        NSLog(@"消费了一个产品,现有产品: %d个",self.count);
        [self.iCondition unlock];
    });
}

NSConditionLock 条件锁

互斥锁

NSConditionLock 是对 NSCondition 的二次封装,自带条件判断,可用来控制线程执行顺序

// 创建 ,初始条件为 1 的 锁
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
// 加锁
[lock lock];
// 解锁
[lock unlock];
// 加锁,只有当 锁的条件 == 1时,才会执行锁后面的代码
[lock lockWhenCondition:1];
// 解锁,并改变条件值 == 2
[lock unlockWithCondition:2];

使用示例

NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:3];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [lock lockWhenCondition:3];
    NSLog(@"线程 1");
    [lock unlockWithCondition:2];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [lock lockWhenCondition:1];
    NSLog(@"线程 3");
    [lock unlockWithCondition:0];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [lock lockWhenCondition:2];
    NSLog(@"线程 2");
    [lock unlockWithCondition:1];
});

/*
打印结果一定是:
线程 1
线程 2
线程 3

执行顺序
初始条件 == 3, 
执行[lock lockWhenCondition:3]后代码
打印“线程 1”
然后[lock unlockWithCondition:2];, 使得 条件 = 2
条件 == 2,
执行[lock lockWhenCondition:2] 后代码
打印“线程 2”
然后[lock unlockWithCondition:1];, 使得 条件 = 1
条件 == 1, 
执行[lock lockWhenCondition:1] 后代码
打印“线程 3”
然后[lock unlockWithCondition:0];, 使得 条件 = 0
*/

NSRecursiveLock 递归锁

互斥锁

同一线程 中,支持递归调用。

多线程中还是可能产生死锁。递归锁可用多次加锁,可能导致多个条线程拥有这把锁,而解锁的条件是只有当前线程拥有这把锁,如果有多个线程持有了,就可能相互等待,导致死锁。

// 创建
NSRecursiveLock *lock = [NSRecursiveLock new];
// 加锁
[lock lock];
// 解锁
[lock unlock];
// 尝试加锁
BOOL s = [lock tryLock];

示例

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    static void (^recursiveMethod)(int);
    recursiveMethod = ^(int value){
        if (value > 0) {
            [self.iRecursiveLock lock];
            NSLog(@"%d",value);
            recursiveMethod(value - 1);
            [self.iRecursiveLock unlock];
        }
    };
    recursiveMethod(10);
});

pthread_mutex

大部分的锁都是对 pthread_mutex 的封装

// 初始化锁,pthread_mutexattr_t可用来设置锁的类型。
pthread_mutex_init(pthread_mutex_t mutex,const pthread_mutexattr_t attr)
//加锁
pthread_mutex_lock(pthread_mutex_t mutex);
//尝试加锁,当锁已经在使用的时候,返回为EBUSY,而不是挂起等待,成功返回0.失败返回错误信息
pthread_mutex_trylock(*pthread_mutex_t *mutex);
//释放锁
pthread_mutex_unlock(pthread_mutex_t *mutex);
//使用完锁之后释放锁
pthread_mutex_destroy(pthread_mutex_t* mutex);
//设置互斥锁的范围
pthread_mutexattr_setpshared();
//获取互斥锁的范围
pthread_mutexattr_getpshared();

dispatch_semaphore_t 信号量

// 这个函数是创建一个dispatch_semaphore_t类型的 信号量,并且创建的时候需要指定信号量的大小 
dispatch_semaphore_t sem = dispatch_semaphore_create(0); 
// 发送信号量。该函数会对信号量的值进行 +1 操作 
dispatch_semaphore_signal(sem);
// 等待信 号量。如果信号量值 == 0,那么该函数就会一直等待,也就是不返回(相当于阻塞当前线程), 
// 直到该函数等待的信号量的值 >= 1,该函数会对信号量的值进行 -1 操作,然后返回。 
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

详细介绍可见 OC底层探索 - 多线程 & GCD

@synchronized 同步锁

互斥锁

递归锁,支持多线程加锁

@synchronized(nil) 时锁不会生效

详细介绍可见 OC底层探索 - @synchronized

各种锁加锁解释锁耗时对比

注意: 这只是一次运行的结果,每次运行耗时不同,只能表示个大概。

---------- 运行 (1000) 次 ----------
OSSpinLock:                   0.15 ms
dispatch_semaphore:           0.17 ms
pthread_mutex:                0.19 ms
os_unfair_look:               0.19 ms
NSCondition:                  0.40 ms
NSLock:                       0.38 ms
pthread_mutex(recursive):     0.34 ms
NSRecursiveLock:              0.58 ms
NSConditionLock:              1.45 ms
@synchronized:                0.93 ms
---------- 运行 (10000) 次 ----------
OSSpinLock:                   0.82 ms
dispatch_semaphore:           0.87 ms
pthread_mutex:                1.39 ms
os_unfair_look:               1.12 ms
NSCondition:                  2.22 ms
NSLock:                       2.23 ms
pthread_mutex(recursive):     2.06 ms
NSRecursiveLock:              3.33 ms
NSConditionLock:              6.91 ms
@synchronized:                4.49 ms
---------- 运行 (100000) 次 ----------
OSSpinLock:                  15.83 ms
dispatch_semaphore:           9.24 ms
pthread_mutex:               10.70 ms
os_unfair_look:              10.43 ms
NSCondition:                 15.88 ms
NSLock:                      14.04 ms
pthread_mutex(recursive):    12.60 ms
NSRecursiveLock:              5.53 ms
NSConditionLock:             11.49 ms
@synchronized:               12.52 ms
---------- 运行 (1000000) 次 ----------
OSSpinLock:                  77.15 ms
dispatch_semaphore:          59.11 ms
pthread_mutex:               43.83 ms
os_unfair_look:              35.18 ms
NSCondition:                 50.60 ms
NSLock:                      42.96 ms
pthread_mutex(recursive):    56.78 ms
NSRecursiveLock:             59.84 ms
NSConditionLock:            117.55 ms
@synchronized:              129.87 ms
---------- 运行 (10000000) 次 ----------
OSSpinLock:                 301.97 ms
dispatch_semaphore:         323.77 ms
pthread_mutex:              376.66 ms
os_unfair_look:             374.91 ms
NSCondition:                347.86 ms
NSLock:                     363.72 ms
pthread_mutex(recursive):   498.09 ms
NSRecursiveLock:            576.16 ms
NSConditionLock:           1106.98 ms
@synchronized:             1374.10 ms