iOS 中的锁总结

线程锁是为了保护共享资源的一种机制;对共享资源进行访问的代码块称为临界区

自旋锁

自旋锁在任一时刻只能有一个持有者(只能有一个线程对象获得锁),如果资源已经被占用(自旋锁已经被别的线程持有),新的资源申请者会一直循环检查锁是否已经被释放,因此得名自旋

自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁容易产生两个问题:

  • 死锁:如果试图递归的获取自旋锁必定会导致死锁
  • 占用资源:如果没有限制,新的资源申请者会不停的循环等待,容易导致CPU资源占用过高

自旋锁只适用于每次锁的时间比较短暂的情况;因为新的资源申请者不停的循环等待,所以性能较好

原理:理论上来说只要定义一个全局变量,用来表示锁的可用情况即可

1
2
3
4
5
6
7
8
9
Bool lock = false; // 一开始没有锁上,任何线程都可以申请锁
do {
while(lock){ // 如果 lock 为 true 就一直死循环,相当于申请锁
lock = true; // 挂上锁,这样别的线程就无法获得锁
// Critical section // 临界区
lock = false; // 相当于释放锁,这样别的线程可以进入临界区
// Reminder section // 不需要锁保护的代码
}
}

OSSpinLock

1
2
3
4
OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
// do something
OSSpinLockUnlock(&lock);

OSSpinLock 已经不再安全,主要原因发生在低优先级线程拿到锁时,高优先级线程进入忙等(busy-wait)状态,消耗大量 CPU 资源,从而导致低优先级线程拿不到 CPU 资源从而无法完成任务并释放锁,造成死锁状态。这种问题被称为优先级反转。

os_unfair_lock (iOS_10.0 +)

1
2
3
4
5
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
// do something
os_unfair_lock_unlock(unfairLock);

互斥锁

互斥锁和自旋锁类似,在任意时刻只能有一个持有者,新的资源申请对象只能等待。不同的点在于互斥锁的等待线程会进入睡眠状态,消耗CPU资源较低,但是由于线程需要唤醒,所以在加锁时间较短时性能不佳

pthread_mutex

1
2
3
4
5
6
pthread_mutex_t lock;
pthread_mutex_init(&lock, nil);
pthread_mutex_lock(&lock);
// do ...
pthread_mutex_unlock(&lock);
pthread_mutex_destroy(&lock);

一般情况下,一个线程只能申请一次锁,也只能在获得锁的情况下才能释放锁,多次申请锁或释放未获得的锁都会导致崩溃

假设在已经获得锁的情况下再次申请锁,线程会因为等待锁的释放而进入睡眠状态,因此就不可能再释放锁,从而导致死锁

NSLock

NSLock 是 Objective-C 以对象的形式暴露的一种锁,只是在内部封装了一个属性为 PTHREAD_MUTEX_ERRORCHECK 的 pthread_mutex,,它损失一定性能换来错误提示

@synchronized()

OC 层面的锁,通过牺牲性能换来语法上的简洁与可读。

@synchronized 后面需要紧跟一个 OC 对象,它实际上是把这个对象当做锁来使用。这是通过一个哈希表来实现的,OC 在底层使用了一个互斥锁的数组(可以理解为锁池),通过对对象计算哈希值来得到对应的互斥锁

信号量 dispatch_semaphore_t

信号量是 GCD 模块中的一种锁机制,每次会把信号量的值减 1,判断是否大于0。如果大于零,说明不用等待,所以立刻返回;否则就需要等待

1
2
3
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); 
// do something
dispatch_semaphore_signal(_lock);

信号量的本质是使线程进入睡眠状态等待;当信号量只是0和1时于互斥锁功能相同

递归锁(可重入锁)

递归锁属于互斥锁的一种实现,特点是可以对同一个锁多次加锁而不会导致死锁

pthread_mutex_t

pthread_mutex锁也支持递归,只需要设置PTHREAD_MUTEX_RECURSIVE即可

1
2
3
4
5
6
7
8
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);

NSRecursiveLock

递归锁是通过 pthread_mutex_lock 函数来实现,在函数内部会判断锁的类型,如果显示是递归锁,就允许递归调用,仅仅将一个计数器加一,锁的释放过程也是同理

NSRecursiveLock 与 NSLock 的区别在于内部封装的 pthread_mutex_t 对象的类型不同,前者的类型为 PTHREAD_MUTEX_RECURSIVE

读写锁

读写锁将读写分离,例如只读锁只会在读取时加锁,同理可以单独在写的时候加锁

pthread_rwlock_rdlock

1
2
3
4
5
6
7
8
//加读锁
pthread_rwlock_rdlock(&rwlock);
//解锁
pthread_rwlock_unlock(&rwlock);
//加写锁
pthread_rwlock_wrlock(&rwlock);
//解锁
pthread_rwlock_unlock(&rwlock);

条件锁

条件锁的底层是通过条件变量(condition variable) pthread_cond_t 来实现的
条件变量有点像信号量,提供了线程阻塞与信号机制,因此可以用来阻塞某个线程,并等待某个数据就绪,随后唤醒线程

条件变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void consumer () { // 消费者  
pthread_mutex_lock(&mutex);
while (data == NULL) {
pthread_cond_wait(&condition_variable_signal, &mutex); // 等待数据
}
// handle new data
pthread_mutex_unlock(&mutex);
}

void producer () {
pthread_mutex_lock(&mutex);
// 生产数据
pthread_cond_signal(&condition_variable_signal); // 发出信号给消费者,告诉他们有了新的数据
pthread_mutex_unlock(&mutex);
}

NSCondition

NSCondition 实质上是封装了一个互斥锁和一个条件变量, 它把前者的 lock 方法和后者的 wait/signal 统一在 NSCondition 对象中,暴露给使用者

1
2
3
4
5
6
7
8
- (void)signal {
pthread_cond_signal(&_condition);
}

// 其实这个函数是通过宏来定义的,展开后就是这样
- (void)lock {
int err = pthread_mutex_lock(&_mutex);
}

NSConditionLock

借助 NSCondition 来实现,它的本质就是一个生产者-消费者模型。“条件被满足”可以理解为生产者提供了新的内容。NSConditionLock 的内部持有一个 NSCondition 对象,以及 _condition_value 属性,在初始化时就会对这个属性进行赋值

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2017-2021 HonQi

请我喝杯咖啡吧~