iOS Lock

锁 🔒

锁用于解决多线程间资源共享的安全问题

分类:

  • 自旋锁(Spinlock): 即忙等,类似 do-while,会一直占着CPU、内存等即忙碌状态
  • 互斥锁(Mutex): 闲等,即锁的线程处于休眠状态
    • 非递归锁(non-recursive mutex)或称不可重入互斥锁(non-reentrant mutex): 同一时刻只能被一条线程所拥有
    • 可递归锁(recursive mutex)或称可重入互斥锁(reentrant mutex): 同一时刻能被多条线程所拥有
  • 读写锁: 多读单写、读和写要互斥,有空再补

iOS中常见方案:

OSSpinLock: 自旋锁

自iOS10移除,被os_unfair_lock替代,因为优先级反转(Priority inversion)问题,优先级高的线程会被系统持续优先调度,所以优先级低的线程一直在等待,无法执行任务

1
2
3
4
OSSpinLock lock = OS_SPINLOCK_INIT; // 初始化
OSSpinLockLock(&lock); // 加锁
OSSpinLockUnlock(&lock); // 解锁
OSSpinLockTry(&lock); // 尝试上锁,false即失败,锁被其他线程持有

os_unfair_lock: 互斥锁

iOS10开始支持,用以替代不安全的OSSpinLock;
等待锁的线程会处于休眠状态,

1
2
3
4
os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT; // 初始化
os_unfair_lock_lock(&unfairLock); // 加锁
os_unfair_lock_unlock(&unfairLock); // 解锁
os_unfair_lock_trylock(&unfairLock); // 尝试上锁

pthread_mutex: 互斥锁

线程在等待锁时会处于休眠状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//非递归
pthread_mutex_t lock0;
pthread_mutex_init(&lock0, NULL); // 初始化
pthread_mutex_lock(&lock0); // 加锁
// ...
pthread_mutex_unlock(&lock0); // 解锁
pthread_mutex_destroy(&lock0); // 释放

//递归
pthread_mutex_t lock1;
pthread_mutexattr_t attr; // 用于设置锁的类型
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 递归
pthread_mutex_init(&lock1, &attr); // 初始化
pthread_mutexattr_destroy(&attr); // 释放
pthread_mutex_lock(&lock1); // 加锁
// ...
pthread_mutex_unlock(&lock1); // 解锁
pthread_mutex_destroy(&lock1); // 释放

NSLock: 再熟悉不过的吧,遵循NSLocking协议

1
2
3
4
5
NSLock *aLock = [[NSLock alloc] init];
[aLock lock];
[aLock unlock];
[aLock tryLock]; // 尝试加锁 返回Bool
[aLock lockBeforeDate: NSDate.date]; // 能否在指定时间点之前获取锁

NSCondition: 互斥锁,遵循NSLocking协议

存在虚假唤醒问题,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (void)testConditionLock {
for (int i = 0; i < 50; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self testProduction];
});
}
for (int i = 0; i < 100; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self testConsumption];
});
}
}
- (void)testProduction {
[self.conditionLock lock]; // 加锁
self.count ++;
NSLog(@"生产了一个, 现有还有 %d 个", self.count);
[self.conditionLock signal]; // 唤醒一个正在休眠的线程,如果要唤醒多个,需要调用多次。如果没有符合条件的线程则什么也不做。使用时必须已加锁。
[self.conditionLock unlock]; // 解锁
}
- (void)testConsumption {
[self.conditionLock lock]; // 加锁
while (self.count == 0) {
// 使用while而非if,是用于解决虚假唤醒(一次signal唤醒多个wait)问题,
// 即当次线程被唤醒则再去检查count个数是否有值,否则继续进入wait状态,
// 这样也就被唤醒多个的线程只有一个能正常走出此while循环进入下一步消耗的步骤
[self.iCondition wait]; // 阻塞当前线程,使线程进入休眠,等待唤醒信号。使用时必须已加锁
}
self.count --;
NSLog(@"消耗了一个, 现有还有 %d 个", self.count);
[self.conditionLock unlock]; // 解锁
}

NSConditionLock:互斥锁,遵循NSLocking协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var conditionLock = [[NSConditionLock alloc] initWithCondition:10]; 
// 初始化,默认给一个条件
// 加锁: 当lockWhenCondition的条件满足当前锁的条件时加锁,比如下面的10,等于初始时的条件10
// 解锁: unlockWithCondition,重置锁的条件,
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.conditionLock lockWhenCondition:10]; // 满足匹配条件时加锁
NSLog(@"线程 1");
[self.conditionLock unlockWithCondition:2]; // 解锁, 并重置条件
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.conditionLock lockWhenCondition:2]; // 满足匹配条件时加锁
NSLog(@"线程 2");
[self.conditionLock unlockWithCondition:1]; // 解锁, 并重置条件
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.conditionLock lockWhenCondition:1]; // 满足匹配条件时加锁
NSLog(@"线程 3");
[self.conditionLock unlockWithCondition:0]; // 解锁, 并重置条件
});

NSRecursiveLock: 可重入互斥锁,遵循NSLocking协议

递归锁,就是字面意思,可以递归加锁加锁加锁 再解锁解锁解锁的锁

1
2
3
4
5
6
7
8
9
10
11
12
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^recursiveBlock)(int);
recursiveBlock = ^(int value){
if (value > 0) {
[self.recursiveLock lock]; // 加锁
NSLog(@"value: %d", value);
recursiveBlock(value - 1);
[self.recursiveLock unlock]; // 解锁
}
};
recursiveBlock(10);
});

@synchronized: 可重入互斥锁,最简单的锁

1
2
3
4
5
6
7
8
9
10
11
12
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^recursiveBlock)(int);
recursiveBlock = ^(int value){
@synchronized (self) {
if (value > 0) {
NSLog(@"value: %d", value);
recursiveBlock(value - 1);
}
}
};
recursiveBlock(10);
});

读写锁:也称“共享-互斥锁”、多读者-单写者锁

读操作可并发重入,写操作是互斥的,而且读和写的操作也是互斥的。

  • 同一时刻要么写的操作,要么读的操作。

  • 若是写,则最多只能有一个线程在写;若是读,则可以多个线程同时进行读

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 并发队列,因为需要多读
    dispatch_queue_t queue = dispatch_queue_create("pushLock", DISPATCH_QUEUE_CONCURRENT);
    // 读操作,同步可以直接获取结果
    dispatch_sync(queue, ^{
    // 读操作
    });
    // 写操作,栅栏函数可以保证此时只有写操作,异步可以避免耗时操作阻塞线程
    dispatch_barrier_async(queue, ^{
    // 写操作,可能耗时
    });

学术名词百科:

虚假唤醒
自旋锁
优先级倒置

文章目录
  1. 1. 锁 🔒
    1. 1.0.1. iOS中常见方案:
      1. 1.0.1.1. OSSpinLock: 自旋锁
      2. 1.0.1.2. os_unfair_lock: 互斥锁
      3. 1.0.1.3. pthread_mutex: 互斥锁
      4. 1.0.1.4. NSLock: 再熟悉不过的吧,遵循NSLocking协议
      5. 1.0.1.5. NSCondition: 互斥锁,遵循NSLocking协议
      6. 1.0.1.6. NSConditionLock:互斥锁,遵循NSLocking协议
      7. 1.0.1.7. NSRecursiveLock: 可重入互斥锁,遵循NSLocking协议
      8. 1.0.1.8. @synchronized: 可重入互斥锁,最简单的锁
      9. 1.0.1.9. 读写锁:也称“共享-互斥锁”、多读者-单写者锁
    2. 1.0.2. 学术名词百科: