锁
# 分类
# 互斥锁
属于sleep-waiting类型的锁,线程A获取到锁,在释放锁之前,其他线程都获取不到锁。互斥锁也分为两种:
- 递归锁:可重入锁,同一个线程在锁释放前可再次获取锁,即可以递归调用
- 非递归锁:不可重入,必须等锁释放后才能再次获取锁
# 自旋锁
线程A获取到锁,在释放锁之前,线程B又来获取锁,此时获取不到,线程B就会不断的进入循环,一直检查锁是否已被释放,如果释放,则能获取到锁。
# 区别
互斥锁:当线程获取锁但没有获取到时,线程会进入休眠状态,等锁被释放时,线程会被唤醒,同时获取到锁,继续执行任务,互斥锁会改变线程的状态。线程从sleep(加锁)—>running(解锁)的过程中,有上下文的切换,cpu的抢占,信号的发送等开销。
自旋锁:当线程获取锁但没获取到时,不会进入休眠,而是一直循环,线程始终处于活跃状态,不会改变线程状态,也就是忙等。线程一直是running(加锁—>解锁),死循环检测锁的标志位。递归调用自旋锁一定会死锁。
互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长
# 适用场景
互斥锁会改变线程的状态,使得内核不断的调度线程资源,因此效率上比自旋锁要低很多,不适合使用自旋锁的场景都使用互斥锁。
自旋锁在线程的等待过程中是活跃的,避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。因此自旋锁适合用于短时间内的轻量级锁定,主要用在临界区持锁时间非常短且CPU资源不紧张的情况下。
# 常见锁
# NSLock
NSLock是Cocoa提供给我们最基本的锁对象,也是我们经常使用的,除了lock和unlock方法外,还提供了tryLock和lockBeforeDate:两个方法,前一个方法会尝试加锁,如果锁不可用(已经被锁住了),并不会阻塞线程,直接返回NO。lockBeforeDate方法会在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO
# @synchronized
@synchronized指令使用参数对象为该段唯一标示,只有当标示相同时,才满足互斥。
@synchronized指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制。但作为一种预防措施,@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,可以考虑使用锁对象
# pthread_mutex_t
c语音实现,在pthread.h头文件中,mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
pthread_mutex_t lock;
pthread_mutex_init(&lock, NULL);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
2
3
4
# os_unfair_lock
苹果官方推荐的替换OSSpinLock的方案,但是它在iOS10.0以上的系统才可以调用。os_unfair_lock是一种互斥锁,它不会向自旋锁那样忙等,而是等待线程会休眠。
# OSSPinkLock
自旋锁,忙等。
也正是由于它是自旋锁,所以容易发生优先级反转的问题。
当一个低优先级线程获得锁的时候,如果此时一个高优先级的系统到来,那么会进入忙等状态,不会进入睡眠,此时会一直占用着系统CPU时间,导致低优先级的无法拿到CPU时间片,从而无法完成任务也无法释放锁。除非能保证访问锁的线程全部处于同一优先级,否则系统所有的自旋锁都会出现优先级反转的问题
# pthread_rwlock
读写锁
读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为 在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的。
一次只有一个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁。正是因为这个特性pthread_rwlock 适合于对数据结构的读次数比写次数多得多的情况。因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁又叫共享-独占锁。
# NSRecuresiveLock
递归锁。平常我们在代码中使用锁的时候,最容易犯的一个错误就是造成死锁,而容易造成死锁的一种情形就是在递归或循环中。在递归或者循环代码中使用锁容易造成死锁,可以将NSLock换成NSRecursiveLock,pthread_mutex_t设置atrr。
NSRecursiveLock类定义的锁可以在同一线程中多次lock,而不会造成死锁。递归锁会跟踪它被多少次lock,每次成功的lock都必须调用平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
# pthread_mutex_t(recuresive)
也支持递归,只需要设置PTHREAD_MUTEX_RECURSIVE即可
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);
2
3
4
5
6
7
8
# NSCondition
条件锁,遵循NSLocking协议,使用的时候同样是lock,unlock加解锁,wait是傻等,waitUntilDate:方法是等一会,都会阻塞掉线程,signal是唤起一个在等待的线程,broadcast是广播全部唤起。
# NSConditonLock
条件锁,NSConditionLock也跟其他的锁一样,需要lock和unlock对应的,只是lock、lockWhenCondition: 与unlock、unlockWithCondition: 是可以随意组合的。
# dispatch_semaphore
信号量,一般情况下,访问性能比NSMutableArray低,但比使用@synchronize、NSLock、pthread_mutex_t高
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphore);
2
3
只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一。
一句话value>=0
# NSDistributedLock
分布式锁,macOS中的,用于多个进程或者多个程序之间需要构建互斥的情况。NSDistributedLock的实现是通过文件系统的,所以它可以有效的实现不同进程之间的互斥。
# atomic
atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁。并不能保证使用属性的过程是线程安全的。
getter和setter中使用了spinlock_t
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
而spinlock_t内部使用的是os_unfair_lock
using spinlock_t = mutex_tt<LOCKDEBUG>;
template <bool Debug>
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
void lock() {
lockdebug_mutex_lock(this);
// <rdar://problem/50384154>
uint32_t opts = OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION | OS_UNFAIR_LOCK_ADAPTIVE_SPIN;
os_unfair_lock_lock_with_options_inline
(&mLock, (os_unfair_lock_options_t)opts);
}
void unlock() {
lockdebug_mutex_unlock(this);
os_unfair_lock_unlock_inline(&mLock);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 性能(从高到低)
- os_unfair_lock
- OSSpinLock
- dispatch_semaphore
- pthread_mutex_t(default)
- NSLock
- NSCondition
- pthread_mutex_t(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized