各种锁

44 阅读2分钟

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添加到主队列,会造成死锁