基础
分类
- 自旋锁
- 发现其他线程执行,当前线程忙等,耗费性能较高
- OSSpinLock
- 互斥锁
- 发现其他线程执行,当前线程休眠Runnable
- 保证锁内的代码,同一时间,只有一条线程能执行
- 互斥锁的锁定范围应该尽量小,锁定范围越大,效率越差
- 常见锁
- NSLock
- pthread_mutex
- @synchronized
- 读写锁
- 一种特殊的自旋锁。
- 相对于自旋锁而言能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源(最大可能的读者数为实际的逻辑CPU数量)。写者是排他性的
- 同时只能有一个写者或多个读者
- 把对共享资源的访问者分为 读者 和 写者。
- pthread_rwlock_t
- 上层封装实现的其他锁分类
- 条件锁:条件变量,当进程的某些资源要求不满足时就进入休眠(锁住)。当资源被分配到了,条件锁打开,进程继续运行。
- NSCondition
- NSConditionLock
- 递归锁:同一个线程可以加锁N次而不会引发死锁
- NSRecursiveLock
- pthread_mutex(recursive)
- 信号量semaphore:互斥锁可以说是semaphore在仅取值0/1时的特例。
- atomic
- 原子属性,为多线程开发准备的,是默认属性
- iOS 10之前,属性的atomic底层实现用的是自旋锁,仅仅在属性的setter方法中,增加了自旋锁【保证同一时间,只有一条线程对属性进行赋值操作】
- iOS 10之后,属性的atomic底层实现用的是互斥锁
- 在objc源码中可以在reallySetProperty方法找到对atomic的处理
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
八大锁
-
OSSpinLock:自旋锁 iOS10及以后弃用 -
os_unfair_lock:互斥锁(iOS10开始替代OSSpinLock) -
NSLock:互斥锁。基于pthread_mutex -
NSCondition:互斥锁 -
NSConditionLock:互斥锁 -
pthread_mutex:互斥锁(可设置:递归) -
NSRecursiveLock:递归锁 -
@synchronized:递归锁
NSLock & NSRecursiveLock
-
底层实现:
pthread -
NSLock -
NSRecursiveLock
NSCondition
NSCondition的对象实际上作为一个锁和一个线程检查器
- 锁:主要为了当检测条件时保护数据源,执行条件引发的任务
- 线程检查器:主要是根据条件决定是否继续运行线程,即线程是否被阻塞
-
常用API
// 一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只被访问、修改一次,其他线程的命令需要在lock外等待,直到unlock才可以访问
[condition lock];
// 配合lock使用
[condition unlock];
// 让当前线程处于等待状态
[condition wait];
// 让cpu发信号告诉线程不用再等待,可以继续执行
[condition signal];
NSConditionLock
-
NSConditionLock是锁,一旦一个线程获得锁,其他线程一定等待;condition就是整数,内部通过整数比较条件 -
常用API
// 表示lock期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition),那它能执行以下代码;如果已经有其他线程获得锁(条件锁/无条件锁),则等待,直到其它线程解锁
[lock lock];
// 如果没有其他线程获得锁,但是该锁内部的条件不为A,它依然不能获得锁,仍然等待
// 如果内部的条件为A,并没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其它任何线程都将等待代码完成,直到它解锁
[lock lockWithCondition:A条件];
// 表示释放锁,同时把内部的condition设置为A条件。【先更改当前的value值,然后进行广播】
[lock unlockWithCondition:A条件];
// 如果被锁定(没获得锁),并超过该时间则不再阻塞线程。【返回的值是NO,它没有改变锁的状态,这个函数的目的在于可以实现两种状态下的处理】
return = [lock lockWhenCondition:A条件
beforeDate:指定条件];
示例分析
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1];
NSLog(@"线程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
sleep(0.1);
NSLog(@"线程 2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"线程 3");
[conditionLock unlock];
});
输出为:3 -> 2 -> 1
-
线程1调用
[conditionLock lockWhenCondition:],此时因为不满足当前条件。所以进入waiting状态,释放当前的互斥锁 -
此时当前的线程3调用
[conditionLock lock],本质上是调用[conditionLock lockBeforeDate:],这里不需要比对条件值,所以线程3会打印 -
线程2执行
[conditionLock lockWhenCondition:],因为满足条件值,所以线程2会打印,打印完成后调用[conditionLock unlockWhenCondition:],这个时候将value设置为1,并发送broadcast,此时线程1接收到当前的信号,唤醒执行并打印。
递归锁 NSRecursiveLock
-
保证同一线程下重复加锁
-
**在多线程环境下,递归调用会造成死锁,多线程在加锁和解锁中,会出现互相等待解锁的情况。 **
-
与NSLock一样都是基于
pthread_mutex_init实现,只是设置type为递归类型。
递归锁 @synchronized
使用
OC与Swift使用方式
/** objc */
@synchronized({obj}) {
// action
}
/** swift */
// 注意enter和leave的obj必须是同一个
objc_sync_enter({obj})
// action
objc_sync_exit({obj})
加锁时,在缓存获取,不会重复创建。可以在多线程下递归调用。如性能方面要求不是非常高的话,使用该锁还更简便。
数据结构
// SyncData数据结构如下,可以看到是一个链表结构
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData; // 链表,下一个结点
DisguisedPtr<objc_object> object; // 关联到锁的对象
int32_t threadCount; // 使用该block的线程数量
recursive_mutex_t mutex; // 互斥递归锁
} SyncData;
// SyncCache
typedef struct SyncCache {
unsigned int allocated;
unsigned int used;
SyncCacheItem list[0];
} SyncCache;
typedef struct {
SyncData *data;
unsigned int lockCount; // 当前线程锁定这个block的次数
} SyncCacheItem;
原理
recursive_mutex
#import <Foundation/Foundation.h>
@interface YKDemo : NSObject
@end
@implementation YKDemo
- (void)testFunc {
@synchronized (self) {
NSLog(@"Yakamoz");
}
}
@end
将该文件通过clang -x objective-c -rewrite-objc main.m 转成c++代码后查看testFunc的实现
static void _I_YKDemo_testFunc(YKDemo * self, SEL _cmd) {
{ id _rethrow = 0; id _sync_obj = (id)self; objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT {
_SYNC_EXIT(id arg) : sync_exit(arg) {
}
~_SYNC_EXIT() {
objc_sync_exit(sync_exit);
}
id sync_exit;
} _sync_exit(_sync_obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders__v_wkpkp22d6ns5gwlzdyykl4040000gn_T_main_b95c39_mi_0);
} catch (id e) {
_rethrow = e;
}
···
}
}
可以看到有两部分
-
objc_sync_enter(_sync_obj)+objc_sync_exit(sync_exit) -
_fin_force_rethow异常捕获和处理
在objc4源码中找到1的两个方法实现,在方法中可以看到若obj传空则无效,下面只截取重点代码探究
流程
// objc_sync_enter(id obj)
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock();
-
构建
SyncData对象 -
对象内部的
recursive_mutex_t加锁
// objc_sync_exit(id obj)
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
-
构建
SyncData对象 -
若对象为空,即代表没有该锁没有线程在使用,返回错误
-
若对象不为空,对象内部的
recursive_mutex_t解锁
id2data 获取同步信息
那么这两个方法是如何通过调用id2data()函数取得同步信息的呢?
// 1. 快速缓存。检查每条线程的单项快速缓存是否匹配对象,若匹配则返回该对象
switch(why) {
case ACQUIRE: {
// objc_sync_enter时,调用锁的数量+1
++syncLockCount;
break;
}
case RELEASE:
// objc_sync_exit时,调用锁的数量-1
if (--syncLockCount == 0) {
// 若-1后,没有线程再使用该锁,从快速缓存中移除
syncData = nullptr;
// 使获取到的结果的threadCount-1.
// 使用atomic因为可能与并发ACQUIRE发生冲突
AtomicDecrement(&result->threadCount);
}
break;
···
}
// 2. pthread线程缓存。检查已拥有锁的每条线程的缓存是否匹配对象,若匹配则返回该对象
// 2.1 获取pthread线程缓存SyncCache
// 2.2 遍历使用的线程,匹配object
SyncCacheItem *item = &cache->list[i];
// 2.3 若匹配到
switch(why) {
case ACQUIRE:
// objc_sync_enter时,调用锁的数量+1
item->lockCount++;
break;
case RELEASE:
// objc_sync_exit时,调用锁的数量-1
item->lockCount--;
if (item->lockCount == 0) {
// 若-1后,没有线程再使用该锁,从pthread缓存中移除
cache->list[i] = cache->list[--cache->used];
// 同第1步中的操作
AtomicDecrement(&result->threadCount);
}
break;
···
}
// 若线程缓存中都没找到,执行下述第3步
// spinlock_t对下述流程加锁,防止多个线程为同一个新对象创建多个锁。
SyncData* p;
SyncData* firstUnused = NULL;
// 3. 遍历全局列表static StripedMap<SyncList> sDataLists查找匹配的对象
// 3.1 遍历列表,若object匹配,使threadCount+1,跳出循环
// 3.3 若不匹配,使firstUnused指向p
// 3.4 若为objc_sync_exit,跳出循环
// 3.5 否则,将object传给该对象,并初始化threadCount为1,跳出循环
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
// 3.6 全局列表存储
···
// 4. 若快速缓存没有被占用,存储至快速缓存,否则存储至pthread缓存,便于下次查找
···
核心处理总结
- 存储结构
- 全局静态哈希表sDataLists
- 元素为SyncList>SyncData
- 存储方案
- TLS
- Cache
- 加锁、解锁实现
- OSAtomicIncrement32Barrier(&result->threadCount);
- OSAtomicDecrement32Barrier(&result->threadCount);
- 可重入、递归、多线程实现原因
- TLS保障 threadCount标记多少条线程对这个锁对象加锁
- lockCount标记进来了多少次