前言
在开发中经常会碰到一边在写数据,一边的读数据的情况,往往会出现数据混乱甚至崩溃,这就是由线程不安全导致的资源抢夺
问题,这种情况就需要对线程进行加锁就可以搞定,下面将对线程锁进行讲解
锁的分类
在OC
中有两种锁:自旋锁
和互斥锁
自旋锁
-
自旋锁是一种用于保护多线程共享资源的锁,与一般
互斥锁(mutex)
不同之处在于当它尝试获取锁时以忙等待(busy waiting)
的形式不断地循环检查锁
是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠)
,当上一个线程的任务执行完毕,下一个线程会立即执行。在多CPU
的环境中,对持有锁较短
的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。 -
优点:自旋锁
不会引起调用者睡眠
,所以不会
进行线程调度
、CPU
时间片轮转等耗时操作。所有如果能在很短的时间内获得锁
,自旋锁的效率远高于互斥锁 -
缺点:自旋锁
一直占用CPU
,他在未获得锁的情况下一直运行(自旋)占用着CPU
,如果不能在很短的时间内
获得锁,这无疑会使CPU效率降低
总结:效率高,但是一直占用
CPU
耗费资源,不能实现递归调用。
常见的自旋锁:atomic
,OSSpinLock
,dispatch_semaphore_t
互斥锁
- 当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入
睡眠状态等待
任务执行完毕,此时CPU
可以调度其他线程。当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务
。 - 说到互斥后想到同步,同步是只有一个任务执行完了,下个任务才可以执行。
同步:互斥+顺序
常见的互斥锁:@synchronized
,NSLock
,pthread_mutex
,NSConditionLock(条件锁)
,NSCondition(条件锁)
,NSRecursiveLock(递归锁)
锁的性能对比
在网上经常能看到锁的性能图:
-
性能高低对比:
OSSpinLock(自旋锁)
>dispatch_semaphone(自旋锁)
>pthread_mutex(互斥锁)
>NSLock(互斥锁)
>NSCondition(条件锁)
>pthread_mutex(recursive) 互斥递归锁
>NSRecursiveLock(递归锁)
>NSConditionLock(条件锁)
>@synchronized(互斥锁)
-
@synchronized
是我们比较常用的,以前他的新能很低,但是后面苹果对它做了 优化,收集一些锁分别在100000
次循环中执行,然后用CFAbsoluteTimeGetCurrent
分别计算执行完毕的时间得到: -
iphone11
模拟器: -
iphone11
真机:
通过对比发现,在真机中锁的性能都有了显著的提升,就拿@synchronized
来说,由于它的“出勤率”还是挺高的,苹果终究还是对它进行了优化。
锁的作用
来模拟下卖票的场景:
- (void)testGlobalQueueSell {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self sellingTickets];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 8; i++) {
[self sellingTickets];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self sellingTickets];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 12; i++) {
[self sellingTickets];
}
});
}
- (void)sellingTickets {
if (self.remainTickets > 0) {
self.remainTickets--;
sleep(1);
NSLog(@"卖了一张,当前余票: %d", self.remainTickets);
} else {
NSLog(@" 车票已经卖完 ");
}
}
// 调用
self.remainTickets = 30;
[self testGlobalQueueSell];
复制代码
得到结果如下:
由于有多个线程在卖票,导致同一时间卖了多张票而不知道实际票数,进而就出现了数据混乱的问题,这个时候就需要加锁来保证线程安全
- (void)sellingTickets {
@synchronized (self) {
if (self.remainTickets > 0) {
self.remainTickets--;
sleep(1);
NSLog(@"卖了一张,当前余票: %d", self.remainTickets);
} else {
NSLog(@" 车票已经卖完 ");
}
}
}
复制代码
这里选择的是互斥锁@synchronized
,保证同一时间只能有一个线程在处理票的数量改变,结果如下:
此时剩余票数就显示正常了,问题得以解决,那么@synchronized
是怎么保证线程安全的呢,接下来我们将走进源码 objc4-818.2 进行分析
@synchronized
原理
直接在源码中搜索synchronized
发现搜不到,说明底层并不是直接用它分析,必定有其他的结构,我们可以使用xcrun
查看源码:
{ id _rethrow = 0;
id _sync_obj = (id)appDelegateClassName; // 传入的参数
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);
} catch (id e) {_rethrow = e;}
{ struct _FIN { _FIN(id reth) : rethrow(reth) {}
~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
id rethrow;
} _fin_force_rethow(_rethrow);}
}
复制代码
- 在源码里
@synchronized
先会调用objc_sync_enter
函数,然后调用结构体_SYNC_EXIT
,它有构造方法
和析构方法
,构造方法会调用sync_exit
函数,析构方法会调用objc_sync_exit
,于是研究的重心就放在objc_sync_enter
和objc_sync_exit
上面
结构分析
-
objc_sync_enter
和objc_sync_exit
源码如下:int objc_sync_enter(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, ACQUIRE); ASSERT(data); data->mutex.lock(); } else { // @synchronized(nil) does nothing if (DebugNilSync) { _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug"); } objc_sync_nil(); } return result; } int objc_sync_exit(id obj) { int result = OBJC_SYNC_SUCCESS; if (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; } } } else { // @synchronized(nil) does nothing } return result; } 复制代码
通过观察发现二者的处理基本一致,数据类型都是
SyncData
,objc_sync_enter
时会调用data->mutex.lock()
进行加锁,objc_sync_exit
时会调用data->mutex.tryUnlock()
进行解锁,两个data
的区别只是类型不同。那么研究对象就来到了SyncData
上面 -
SyncData
的结构如下:typedef struct alignas(CacheLineSize) SyncData { struct SyncData* nextData; DisguisedPtr<objc_object> object; int32_t threadCount; // number of THREADS using this block recursive_mutex_t mutex; } SyncData; 复制代码
-
它是一个结构体:
nextData
也是SyncData
类型,说明它是一个单向链表
object
是关联对象threadCount
记录线程的数量,也就是可以多线程访问mutex
:是是递归锁
结论:
@synchronized
是一把可多线程
、可递归
的互斥锁
再来分析SyncData
的获取id2data
函数:
-
id2data
源码分析如下:-
- 首先创建了一个
spinlock_t
锁,再对SyncData
进行内存创建与赋值时加锁,保证线程安全
- 首先创建了一个
-
- 然后通过
LIST_FOR_OBJ
获取链表,它的结构如下:
#define LIST_FOR_OBJ(obj) sDataLists[obj].data static StripedMap<SyncList> sDataLists; // StripedMap结构 class StripedMap { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; #endif } 复制代码
可以得出
sDataLists
是一个全局类型的哈希表,存储的是SyncList
,它在真机环境容量为8
,模拟器下容量为64
,SyncList
的结构如下:struct SyncList { SyncData *data; spinlock_t lock; constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { } }; 复制代码
而
SyncData
是一个单向链表,所以可以得到StripedMap
的结构: - 然后通过
-
- 获取
SyncData
根据why
条件进行相关处理
- 获取
-
- 如果没有获取到
SyncData
数据,就会先进行加锁然后创建并进行相关赋值
- 如果没有获取到
-
- 最后执行
done
进行相关的存储
- 最后执行
-
但具体是怎么走的流程,得需要结合案例分析
结合案例分析
由于SyncData
支持多线程且可递归
,可以分为以下4类
进行分析,在模拟器下将StripeCount
调成1
,增大哈希冲突的概率
单线程递归同一个object
void oneThreadOneObject() {
LGPerson *p = [[LGPerson alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (p) {
NSLog(@"0");
@synchronized (p) {
NSLog(@"1");
@synchronized (p) {
NSLog(@"2");
}
}
}
});
}
复制代码
-
分别在
@synchronized
处打断点,然后在 objc4-818.2 中运行,来到断点后在id2data
函数的data
判断处,加锁处理处,解锁后的处理处都打上断点 -
- 第一进来时
StripedMap
的64
个SyncList
都是空的,所以第一次获取不到data
和cache
数据
然后就会来到加锁创建处,由于*listp
为空,所以不会走进循环
于是就会走到posix_memalign
创建处
这里主要是对result
进行:- 分配内存空间,
- 然后关联对象
- 然后多线程数设置为
1
- 创建递归锁
- 然后头插法设置
nextData
,第一个的nextData
为空,并且*listp
指向自己
result
的相关处理后,就会将result
保存到当前的线程空间至此第一次进入完成,继续断点后走到第二个
@synchronized
进入 - 第一进来时
-
- 第二次进入时
StripedMap
就有值了,由于是同一个对象,所以listp
也是有值的
- 下面继续执行,会根据
SYNC_DATA_DIRECT_KEY
获取上一次进入时存储的数据,由于是同一个对象,所以会走进object
辨别,根据why
传入的参数为ACQUIRE
进行lockCount++
处理和lockCount
存储
断点继续会进行第三次加锁
- 第二次进入时
-
- 第三次进入情况和第二次进入一样,最终
lockCount++
继续断点将会走到
objc_sync_exit
时的处理 - 第三次进入情况和第二次进入一样,最终
-
- 解锁前
id2data
处理:由于此时data
有值,最终会走到RELEASE
进行lockCount--
操作
- 此时会对
lockCount--
并进行存储,当为0
时就会清空存储并调用OSAtomicDecrement32Barrier
进行原子操作-1
- 解锁前
单线程递归不同object
案例如下
void oneThreadAnyObject() {
LGPerson *p1 = [[LGPerson alloc] init];
LGPerson *p2 = [[LGPerson alloc] init];
LGPerson *p3 = [[LGPerson alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (p1) {
NSLog(@"0");
@synchronized (p2) {
NSLog(@"1");
@synchronized (p3) {
NSLog(@"2");
}
}
}
});
}
复制代码
-
第一次
@synchronized
由于StripedMap
为空,所以取不到数据,因此会走创建流程,然后用头插法
将*list
指向result
- 然后进行相关的
result
和lockCount
存储
- 然后进行相关的
-
第二次
@synchronized
就有值了,但此时object
变了,不会走进里面的if
判断,且此时fastCacheOccupied = YES
- 此时会走进
for
循环判断
- 但此时
threadCount = 1
,不会在此处进行赋值,所以又走到了创建处
- 这里创建后进行赋值,再使用头插法进行插入,最后进行
cache
存储
- 此时会走进
-
第三次
@synchronized
,由于是不同object
,所以fastCacheOccupied = YES
后就走到判断cache
处,由于第二次对cache
进行了存储,所以这次可以进入cache
判断- 由于
object
不同,所以continue
跳出循环走到for
循环,但threadCount
此时为1
- 后面走到创建处
- 这里创建后进行赋值,再使用头插法进行插入,最后进行
cache
存储
- 由于
多线程递归同一个object
void anyThreadOneObject() {
LGPerson *p = [[LGPerson alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (p) {
NSLog(@"0");
dispatch_async(dispatch_queue_create("wushuang.concurrent1", DISPATCH_QUEUE_CONCURRENT), ^{
@synchronized (p) {
NSLog(@"1");
dispatch_async(dispatch_queue_create("wushuang.concurrent2", DISPATCH_QUEUE_CONCURRENT), ^{
@synchronized (p) {
NSLog(@"2");
}
});
}
});
}
});
}
复制代码
-
第一次进入和前面的两种情况一样,会走进创建流程,然后用
头插法
将*list
指向result
,最后进行相关存储 -
第二次
@synchronized
时,会走到for
循环处:- 然后将
p
的值赋给result
,并调用OSAtomicIncrement32Barrier
进行threadCount+1
- 最后走到
done
进行相关存储
- 然后将
-
第三次
@synchronized
时,和第二次进入时一样进行threadCount+1
多线程递归不同object
void anyThreadAnyObject() {
LGPerson *p1 = [[LGPerson alloc] init];
LGPerson *p2 = [[LGPerson alloc] init];
LGPerson *p3 = [[LGPerson alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (p1) {
NSLog(@"0");
dispatch_async(dispatch_queue_create("wushuang.concurrent1", DISPATCH_QUEUE_CONCURRENT), ^{
@synchronized (p2) {
NSLog(@"1");
dispatch_async(dispatch_queue_create("wushuang.concurrent2", DISPATCH_QUEUE_CONCURRENT), ^{
@synchronized (p3) {
NSLog(@"2");
}
});
}
});
}
});
}
复制代码
-
第一次进入
@synchronized
没有数据,就会走创建并进行相关存储 -
第二次
@synchronized
时进入for
循环判断,由于此时object
不同,会进行赋值- 最后进行相关存储
-
第三次
@synchronized
时和第二次一样
总结
整个加锁核心处理流程如下:
NSLock
和NSRecursiveLock
NSLock
NSLock
我们比较常见,经常为了解决线程安全使用,先看以下案例:
- (void)threadDemo {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^ testBlock)(int num);
testBlock = ^(int num) {
if (num > 0) {
NSLog(@"current num value %d ", num);
testBlock(num - 1);
}
};
testBlock(10);
});
}
复制代码
-
在全局队列里面是一个
block
,这个很明显按照顺序打印 -
但把代码放在
for
循环,由于打印就出现问题了这个时候就需要
加锁
来解决顺序异常的问题,可以使用NSLock
-
把
NSLock
加载block
块中的业务代码结果怎样呢,结果如下:- 由于加锁后还没解锁,又调用
block
,所以导致了死锁只打印一次,说明NSLock
可以多线程
使用,但不能递归
- 由于加锁后还没解锁,又调用
NSRecursiveLock
- 再来看看
NSRecursiveLock
,它的用法和NSLock
类似
-
在队列函数的块中加锁也是能保证都执行,下面在业务代码的
block
块中加锁试试:- 结果完整的打印一次顺序后就崩溃了,说明
NSRecursiveLock
不支持多线程调用
- 结果完整的打印一次顺序后就崩溃了,说明
-
在文章前面我们分析了
@synchronized
可多线程可递归使用,再用它试试:
- 打印结果显示了
10次
顺序执行,@synchronized
完美的解决了问题
NSCondition
-
NSCondition
的对象实际上作为⼀个锁和⼀个线程检查器:锁主要为了当检测条件时保护数据源
,执⾏条件引发的任务;线程检查器主要是根据条件
决定是否继续运⾏线程
,即线程是否被阻塞。NSCondition
有4个Api
[condition lock]
:⼀般⽤于多线程同时访问
、修改同⼀个数据源
,保证同⼀时间内
数据源只被访问
、修改⼀次
,其他线程的命令需要在lock
外等待,只到unlock
才可访问[condition unlock]
:与lock
同时使用[condition wait]
:让线程处于等待状态[condition signal]
:CPU发信号
告诉线程不⽤在等待,可以继续执⾏
-
案例分析(生产消费模型,例如卖奶茶):
- (void)testCondition { for (int i = 0; i < 50; i++) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self ws_product]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self ws_consumer]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self ws_product]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self ws_consumer]; }); } } - (void)ws_product { self.milkTeaCount += 1; NSLog(@"生产一杯,现有 %zd 杯", self.milkTeaCount); } - (void)ws_consumer { if (self.milkTeaCount == 0) { NSLog(@"等待制作 count: %zd", self.milkTeaCount); } self.milkTeaCount -= 1; NSLog(@"卖了一杯,还剩 %zd", self.milkTeaCount); } 复制代码
输出结果如下:
此时数据明显出现了问题,多次生产数量还是1
,多次消费数量还是0
,要怎么解决呢?此时就需要用条件锁
了,对生产和消费的过程加锁
,当卖完后需要等待生产后再继续卖
Fountion源码解读
-
我们前面分析的
NSLock
,NSRecursiveLock
,NSCondition
这些锁,底层都是用pthread
封装的,那么pthread
底层原理是怎样的我们不得而知,于是查找lock
,看它属于哪个框架能够发现
lock,unlock
是属于NSLocking
协议,原来是使用了协议才能调用,很不幸锁是在Foundation
框架,它是不开源的,怎么搞?此时可以去看swift
的fountion
框架:swift-corelibs-foundation进入源码,找到
NSLock.swift
文件,然后对几种锁进行分析 -
NSLock
分析:在源码中找到NSLock
后,然后找到初始化和加锁解锁的地方init
:调用pthread_mutex_init(mutex, nil)
进行初始化- 加锁:调用
pthread_mutex_lock(mutex)
进行加锁 - 解锁:调用
pthread_mutex_unlock(mutex)
解锁
-
NSRecursiveLock
分析:init
:withUnsafeMutablePointer(to: &attrib) { attrs in pthread_mutexattr_init(attrs) pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE)) pthread_mutex_init(mutex, attrs) } 复制代码
- 加锁:
pthread_mutex_lock(mutex)
- 解锁:
pthread_mutex_unlock(mutex)
通过对比发现NSLock
和NSRecursiveLock
的加锁
和解锁
完全一样,只有初始化时不同,NSRecursiveLock
初始化时,传入一个了PTHREAD_MUTEX_RECURSIVE
类型的attrs
,这也是NSRecursiveLock
可递归 的的原因
NSConditionLock
-
NSConditionLock
也是一种条件锁,一旦一个线程获得锁,其他线程一定等待。主要有以下几个Api
:-
[conditionLock lock]
:表示conditionLock
期待获得锁,如果没有
其他线程获得锁
(不需要判断内部的condition
)那它能执⾏
此⾏以下代码,如果已经有其他线程获得锁
(可能是条件锁,或者⽆条件锁),则等待
直⾄
其他线程解锁
-
[conditionLock unlock]
:解锁
-
[conditionLock lockWhenCondition:A]
:表示如果没有其他线程获得该锁,但是该锁内部的condition
不等于A
条件,它依然不能获得锁,仍然等待
。如果内部的condition
等于A
条件,并且没有其他线程获得该锁,则进⼊代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直⾄它解锁
-
[conditionLock unlockWithCondition:A]
:表示释放锁
,同时把内部的condition
设置为A
条件
-
return = [xxx lockWhenCondition:A beforeDate:t]
:表示如果被锁定(没获得锁),并超过
该时间则不再阻塞线程
。但是注意:返回的值是NO
,它没有改变锁的状态
,这个函数的⽬的在于可以实现两种状态下的处理
-
condition
:所谓的condition
就是整数,内部通过整数⽐较条件
-
-
先来看看案例:
- (void)testConditionLock { NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:2]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [lock lockWhenCondition:1]; NSLog(@"1"); [lock unlockWithCondition:0]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ sleep(1); [lock lockWhenCondition:2]; NSLog(@"2"); [lock unlockWithCondition:1]; }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ [lock lock]; NSLog(@"3"); [lock unlock]; }); } 复制代码
- 打印顺序大概率为:
3 -> 2 -> 1
,由于3
没有加条件只是普通的加锁,创建的条件是2
,只有2
时才能执行条件,然后再根据[lock unlockWithCondition:1]
后执行1
。此时有四个疑问:-
NSConditionLock
和NSCondition
很像,他们有什么区别吗?
-
- 创建时的
2
是什么?
- 创建时的
-
lockWhenCondition
做了什么?
-
unlockWithCondition
做了什么?
-
- 打印顺序大概率为:
-
- 创建代码
initWithCondition:
如下:
- 在
swift
中,其对应的代码如下:
在创建对象时,后先创建NSCondition
类型的成员变量_cond
,然后将condition
赋值给另一个成员变量_calue
- 创建代码
-
lockWhenCondition:
代码如下:
open func lock() { let _ = lock(before: Date.distantFuture) } open func lock(whenCondition condition: Int) { let _ = lock(whenCondition: condition, before: Date.distantFuture) } open func lock(whenCondition condition: Int, before limit: Date) -> Bool { _cond.lock() // _cond调用 lock 加锁 while _thread != nil || _value != condition { // 如果没有_thread 有值或者condition改变了就进入判断 if !_cond.wait(until: limit) { // 如果等待超时则调用解锁,返回false,说明加锁失败,如果没有超时则在while循环中不停判断,直到条件改变跳出循环 _cond.unlock() return false } } #if os(Windows) _thread = GetCurrentThread() #else _thread = pthread_self() // thread对自己加锁 #endif _cond.unlock() // 方法执行完unlock解锁 return true } 复制代码
- 加锁在swift中最终调用的是
lock(whenCondition: before:
方法,方法里先调用NSCondition
的加锁lock
,然后在while
判断,如果没有_thread
有值或者condition
改变了就进入判断,如果等待超时
则调用解锁
,返回false
,说明加锁失败
;如果没有超时
则在while
循环中不停判断
,直到条件改变
跳出循环; 加锁成功后对_thread
进行赋值,也就是对自己进行加锁pthread_self
,方法执行完在返回前unlock
解锁
-
- 解锁
unlockWithCondition
代码如下:
open func unlock(withCondition condition: Int) { _cond.lock() #if os(Windows) _thread = INVALID_HANDLE_VALUE #else _thread = nil // 置空 #endif _value = condition // 改变_value值 _cond.broadcast() _cond.unlock() } 复制代码
- 解锁时先调用
lock
加锁,然后将_thread
置为nil
,再将value
的值改变成新传入的条件condition
,然后方法之前完时调用unlock
,这样就解锁了,但过程中_cond
调用了broadcast
方法,这个是做什么的? broadcast
:open func broadcast() { #if os(Windows) WakeAllConditionVariable(cond) #else pthread_cond_broadcast(cond) #endif } 复制代码
方法里最终调用
pthread_cond_broadcast
,它的作用是唤醒全部阻塞在条件变量上的线程
。这也印证了互斥锁在线程阻塞时休眠,解锁后唤醒执行任务的特点
- 解锁
读写锁(多读单写)
- 读写锁实际是⼀种
特殊的互斥锁
,它把对共享资源的访问者划分成读者
和写者
,读者
只对共享资源
进⾏读
访问,写者
则需要对共享资源
进⾏写
操作。这种锁相对于⾃旋锁⽽⾔能提⾼并发性
,因为在多处理器
系统中,它允许同时
有多个读者来访问
共享资源,最⼤可能的读者数为实际的逻辑CPU
数。写者
是排他性
的,⼀个读写锁
同时只能有⼀个写者
或多个读者
(与CPU
数相关),但不能
同时既有读者⼜有写者。在读写锁保持期间也是抢占失效的。如果读写锁当前没有读者
,也没有写者
,那么写者可以⽴刻获得读写锁
,否则它必须⾃旋
在那⾥,直到没有
任何写者或读者
。如果读写锁没有写者
,那么读者可以⽴即获得该读写锁
,否则读者必须⾃旋在那⾥,直到写者释放
该读写锁。 - 读写锁适合于对数据结构的
读次数⽐写次数多得多
的情况. 因为读模式锁定时可以共享, 以写模式
锁住时意味着独占
, 所以读写锁⼜叫共享-独占锁
读写锁可以用以下pthread_rwlock_t
和栅栏函数dispatch_barrier_async
来实现:
-
pthread_rwlock_t
Api:
pthread_rwlock_init
:初始化锁pthread_rwlock_rdlock
:读加锁pthread_rwlock_tryrdlock
:读尝试加锁pthread_rwlock_wrlock
:写加锁pthread_rwlock_trywrlock
:写尝试加锁pthread_rwlock_unlock
:解锁pthread_rwlock_destroy
:销毁锁
使用方法:
#import <pthread.h>
@property (nonatomic, assign) pthread_rwlock_t rwlock;
@property (nonatomic, strong) NSMutableDictionary *dic;
- (void)testRWLock {
pthread_rwlock_init(&_rwlock, NULL);
self.dic = [NSMutableDictionary dictionary];
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
if (i % 2 == 0) {
[self ws_writeName:[NSString stringWithFormat:@"name_%d", i]];
} else {
[self ws_readName];
}
});
}
}
// 读
- (void)ws_readName {
pthread_rwlock_rdlock(&_rwlock); //读加锁
NSString *name = [self.dic valueForKey:@"name"];
NSLog(@"read name ___ :%@ ?", name);
pthread_rwlock_unlock(&_rwlock); //读解锁
}
// 写
- (void)ws_writeName: (NSString *)name {
pthread_rwlock_wrlock(&_rwlock); // 写加锁
[self.dic setValue:name forKey:@"name"];
NSLog(@"write name ___ :%@ ?", name);
pthread_rwlock_unlock(&_rwlock); // 写解锁
}
复制代码
输出如下:
再来看看栅栏函数的方案
-
dispatch_barrier_async
使用如下
@property (nonatomic, strong) NSMutableDictionary *dic;
@property (nonatomic, strong) dispatch_queue_t myQueue;
- (void)testRWLock {
self.dic = [NSMutableDictionary dictionary];
self.myQueue = dispatch_queue_create("com.current.thread", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
if (i % 2 == 0) {
[self ws_barrier_writeName:[NSString stringWithFormat:@"name_%d", i]];
} else {
[self ws_barrier_readName];
}
});
}
}
// 读
- (void)ws_barrier_readName {
dispatch_sync(self.myQueue, ^{
NSString *name = [self.dic valueForKey:@"name"];
NSLog(@"read name ___ :%@ ?", name);
});
}
// 写
- (void)ws_barrier_writeName: (NSString *)name {
dispatch_barrier_async(self.myQueue, ^{
[self.dic setValue:name forKey:@"name"];
NSLog(@"write name ___ :%@ ?", name);
});
}
复制代码
输出结果如下: