1、自旋锁
概念
它不是通过休眠使线程阻塞,而是在获取锁之前一直处于忙等(即原地打转,称为自旋),使线程处于阻塞状态。
使用场景
锁持有的时间短,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符atomic,本身就有一把自旋锁;
知识点
- os_unfair_lock替代原先的OSSpinLock自旋锁,但是其内部加锁是通过休眠而不是自旋
2、互斥锁
概念
通过休眠的方式,阻塞线程
使用场景
互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差
知识点
- 能够加锁的任意 NSObject 对象;
- 包含NSLock、pthread_mutex;
3、递归锁
概念
同一个线程可以加锁N次而不会引发死锁。递归锁是特殊的互斥锁
包含类型
recursive_mutex_t、NSRecursiveLock、@sychronized
@sychronized
是一个互斥递归锁, 最底层数据结构syncdata是一个单链表,包含对象:nextData、object、threadCount、mutex(递归锁);
加锁解锁底层调用objc_sync_enter和objc_sync_exit,最终都会进入id2data方法;
@synchronized(obj) {
// do work
}
// 会转换成
@try {
objc_sync_enter(obj);
// do work
} @finally {
objc_sync_exit(obj);
}
id2data方法
【第一步】首先在tls(本地局部的线程缓存)中查找
在tls_get_direct方法中以SYNC_DATA_DIRECT_KEY(41)为key,获取与之绑定的SyncData。 如果获取到data,再以SYNC_COUNT_DIRECT_KEY为key获取lockCount,用来记录对象被锁了几次。
根据id2data的第二个参数why判断操作类型:
- ACQUIRE: 加锁,lockCount + 1,同时通过tls_set_direct方法存储;
- RELEASE: 解锁,lockCount -1, tls_set_direct方法调用;如果lockCount <= 0, 则从tls中清除syncData
【第二步】如果不支持tls,则在cache缓存中查找
- 通过fetch_cache方法查找cache缓存中是否有线程;
- 如果有,则遍历cache总表,读取出线程对应的SyncCacheItem;
- 从SyncCacheItem中取出data,然后后续步骤与tls的匹配是一致的;
【第三步】如果cache中也没有,即第一次进来,则创建SyncData,并存储到相应缓存中
- 如果在cache中找到线程,且与object相等,则进行赋值、以及threadCount++;
- 如果在cache中没有找到,则threadCount等于1;
SyncData/SyncCache结构
typedef struct SyncData {
// 传入加锁的对象
id object;
recursive_mutex_t mutex;
struct SyncData* nextData;
int threadCount;
} SyncData;
typedef struct SyncList {
SyncData *data;
spinlock_t lock;
} SyncList;
typedef struct SyncCache {
unsigned int allocated;
unsigned int used;
// list[0]表示当前线程的链表data
SyncCacheItem list[0];
} SyncCache;
typedef struct SyncCacheItem {
SyncData *data;
// 表示线程的加锁次数;
unsigned int lockCount;
} SyncCacheItem;
注意点
- 当锁定对象为空时,相当于未加锁。如果在循环中,可能导致野指针问题;
- 适用于加锁次数少的场景,由于使用链表,当加锁次数多时,会造成查询慢,导致性能降低;
4、读写锁
概念
同一时间,只能有一个写者或多个读者,不能同时存在读者和写者;
实现
同步任务+栅栏
串行队列并不能使用global queue。
// 读任务
- (id)objectForKey:(NSString *)key {
__block id obj;
// 同步读取指定数据:
dispatch_sync(self.concurrent_queue, ^{
obj = [self.dataCenterDic objectForKey:key];
});
return obj;
}
// 写任务
- (void)setObject:(id)obj forKey:(NSString *)key {
// 异步栅栏调用设置数据:
dispatch_barrier_async(self.concurrent_queue, ^{
[self.dataCenterDic setObject:obj forKey:key];
});
}
信号量
@interface ReadWriteLock : NSObject
- (void)readLock;
- (void)readUnlock;
- (void)writeLock;
- (void)writeUnlock;
@end
@implementation ReadWriteLock {
dispatch_semaphore_t _readSemaphore;
dispatch_semaphore_t _writeSemaphore;
NSInteger _readCount;
}
- (instancetype)init {
self = [super init];
if (self) {
_readSemaphore = dispatch_semaphore_create(1);
_writeSemaphore = dispatch_semaphore_create(1);
dispatch_semaphore_signal(_readSemaphore);
dispatch_semaphore_signal(_writeSemaphore);
_readCount = 0;
}
return self;
}
- (void)readLock {
dispatch_semaphore_wait(_readSemaphore, DISPATCH_TIME_FOREVER);
_readCount++;
if (_readCount == 1) {
dispatch_semaphore_wait(_writeSemaphore, DISPATCH_TIME_FOREVER);
}
dispatch_semaphore_signal(_readSemaphore);
}
- (void)readUnlock {
dispatch_semaphore_wait(_readSemaphore, DISPATCH_TIME_FOREVER);
_readCount--;
if (_readCount == 0) {
dispatch_semaphore_signal(_writeSemaphore);
}
dispatch_semaphore_signal(_readSemaphore);
}
- (void)writeLock {
dispatch_semaphore_wait(_writeSemaphore, DISPATCH_TIME_FOREVER);
}
- (void)writeUnlock {
dispatch_semaphore_signal(_writeSemaphore);
}
@end
pthread_relock_init
pthread_rwlock_t lock;
// 初始化锁
pthread_rwlock_init(&lock, NULL);
// 读-加锁
pthread_rwlock_rdlock(&lock);
// 读-尝试加锁
pthread_rwlock_tryrdlock(&lock);
// 写-加锁
pthread_rwlock_wrlock(&lock);
// 写-尝试加锁
pthread_rwlock_trywrlock(&lock);
// 解锁
pthread_rwlock_unlock(&lock);
// 销毁
pthread_rwlock_destroy(&lock);
5、条件锁
条件锁就是条件变量,当线程的某些资源要求不满足条件时就进入休眠;
NSConditionLock、NSCondition;
6、信号量
dispatch_semaphore_t, 信号量取值0、1的特例;
// 创建信号量
dispatch_semaphore_t _readSemaphore = dispatch_semaphore_create(1)
// 释放信号量 0 -> 1 解锁
dispatch_semaphore_signal(_readSemaphore);
// 使用信号量 1 -> 0 加锁
dispatch_semaphore_wait(_readSemaphore, DISPATCH_TIME_FOREVER);
7、死锁
- dispatch_barrier_sync同步栅栏函数进入串行队列中,会造成死锁;
- dispatch_sync进入串行队列,会造成死锁
- dispatch_apply添加到主队列,会造成死锁