资源准备
锁的介绍
锁的类型
-
自旋锁:线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显示释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的:
OSSpinLock
os_unfair_lock
-
互斥锁:是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成:
NSLock
pthread_mutex
@synchronized
-
条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行:
NSCondition
NSConditionLock
-
递归锁:同一个线程可以加锁
N
次而不会引发死锁NSRecursiveLock
pthread_mutex(recursive)
-
信号量(
semaphore
):一种更高级的同步机制,互斥锁可以说是semaphore
在仅取值0/1
时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥:dispatch_semaphore
-
读写锁:一种特殊的自旋锁。它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作:
-
相比自旋锁而言,能提高并发性。因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑
CPU
数。但写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU
数先关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的; -
如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者;
-
如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。
-
其实基本的锁就包括三类:⾃旋锁
、互斥锁
、读写锁
,其他的⽐如条件锁
、递归锁
、信号量
都是上层的封装和实现:
-
互斥锁 =
互斥
+同步
,互斥保证线程安全,当一条线程执行时,其他线程休眠。同步保证执行顺序,多线程串行执行NSLock
、pthread_mutex
、@synchronized
、NSCondition
、NSConditionLock
、NSRecursiveLock
、pthread_mutex(recursive)
、dispatch_semaphore
-
自旋锁 =
互斥
+忙等
,例如do...while
循环。它的优点在于不会引起调用者睡眠,所以不会进行线程调度,CPU
时间片轮转等耗时操作。而缺点是当等待时会消耗大量CPU
资源,所以自旋锁不适用较长时间的任务:OSSpinLock
、os_unfair_lock
锁的性能
2021
年iPhone 12
真机测试,锁的性能对比图:
- 性能从高到低依次排列:
OSSpinLock
(自旋锁)>os_unfair_lock
(自旋锁)>NSCondition
(条件锁)>pthread_mutex
(互斥锁)>NSLock
(互斥锁)>dispatch_semaphore
(信号量)>pthread_mutex(recursive)
(递归锁)>NSRecursiveLock
(递归锁)>@synchronized
(互斥锁)>NSConditionLock
(条件锁)
测试方案
循环十万次,进行加锁和解锁操作。通过开始、结束时间,计算各自锁的耗时:
int kc_runTimes = 100000;
OSSpinLock
:
{
OSSpinLock kc_spinlock = OS_SPINLOCK_INIT;
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
OSSpinLockLock(&kc_spinlock); //解锁
OSSpinLockUnlock(&kc_spinlock);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"OSSpinLock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
dispatch_semaphore_t
:
{
dispatch_semaphore_t kc_sem = dispatch_semaphore_create(1);
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
dispatch_semaphore_wait(kc_sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(kc_sem);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"dispatch_semaphore_t: %f ms",(kc_endTime - kc_beginTime)*1000);
}
os_unfair_lock_lock
:
{
os_unfair_lock kc_unfairlock = OS_UNFAIR_LOCK_INIT;
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
os_unfair_lock_lock(&kc_unfairlock);
os_unfair_lock_unlock(&kc_unfairlock);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"os_unfair_lock_lock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
pthread_mutex_t
:
{
pthread_mutex_t kc_metext = PTHREAD_MUTEX_INITIALIZER;
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
pthread_mutex_lock(&kc_metext);
pthread_mutex_unlock(&kc_metext);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"pthread_mutex_t: %f ms",(kc_endTime - kc_beginTime)*1000);
}
NSLock
:
{
NSLock *kc_lock = [NSLock new];
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
[kc_lock lock];
[kc_lock unlock];
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"NSlock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
NSCondition
:
{
NSCondition *kc_condition = [NSCondition new];
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
[kc_condition lock];
[kc_condition unlock];
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"NSCondition: %f ms",(kc_endTime - kc_beginTime)*1000);
}
PTHREAD_MUTEX_RECURSIVE
:
{
pthread_mutex_t kc_metext_recurive;
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init (&kc_metext_recurive, &attr);
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
pthread_mutex_lock(&kc_metext_recurive);
pthread_mutex_unlock(&kc_metext_recurive);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"PTHREAD_MUTEX_RECURSIVE: %f ms",(kc_endTime - kc_beginTime)*1000);
}
NSRecursiveLock
:
{
NSRecursiveLock *kc_recursiveLock = [NSRecursiveLock new];
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
[kc_recursiveLock lock];
[kc_recursiveLock unlock];
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"NSRecursiveLock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
NSConditionLock
:
{
NSConditionLock *kc_conditionLock = [NSConditionLock new];
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
[kc_conditionLock lock];
[kc_conditionLock unlock];
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"NSConditionLock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
@synchronized
:
{
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
@synchronized(self) {}
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"@synchronized: %f ms",(kc_endTime - kc_beginTime)*1000);
}
iphoneX
真机执行结果:
OSSpinLock: 0.485063 ms
dispatch_semaphore_t: 0.717044 ms
os_unfair_lock_lock: 0.768065 ms
pthread_mutex_t: 0.838041 ms
NSlock: 1.092076 ms
NSCondition: 1.066923 ms
PTHREAD_MUTEX_RECURSIVE: 1.288056 ms
NSRecursiveLock: 1.695037 ms
NSConditionLock: 5.481005 ms
@synchronized: 3.665924 ms
- 可以看到
@synchronized
的执行速度并不是最慢的,说明系统进行了优化。
iphoneX
模拟器执行结果:
OSSpinLock: 0.509977 ms
dispatch_semaphore_t: 0.768065 ms
os_unfair_lock_lock: 0.856042 ms
pthread_mutex_t: 1.099944 ms
NSlock: 1.405954 ms
NSCondition: 1.512051 ms
PTHREAD_MUTEX_RECURSIVE: 1.899004 ms
NSRecursiveLock: 3.090978 ms
NSConditionLock: 4.673004 ms
@synchronized: 5.235076 ms
- 可以看到在模拟器中
@synchronized
的效率低了不少,为什么呢,我们最下面会进行分析。
其中@synchronized
比之前在老版本中的测试结果快了很多,说明官方对其进行了优化
使用模拟器运行,运行结果和真机略有不同,因为系统底层在真机和模拟器上的处理有一些差异。
@synchronized探索
底层实现
我们想对@synchronized
进行分析,先要知道它调用了什么方法,我们可以有两种方式进行探索:1. clang
编译出C++
代码,2. 汇编跟源码。
clang
编译出C++
代码
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
@synchronized (appDelegateClassName) {
}
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
使用xcrun
生成cpp
文件:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
打开main.cpp
文件,找到main
函数的实现:
-
调用
_sync_exit
传入_sync_obj
,相当于调用结构体的构造函数和析构函数。构造函数中没有代码,而析构函数中调用objc_sync_exit
函数,传入的sync_exit
等同于_sync_obj
-
使用
objc_sync_enter(_sync_obj)
函数,进行加锁; -
使用
objc_sync_exit(_sync_obj)
函数,进行解锁
-
-
使用
try...catch
,说明锁的使用有可能出现异常
通过汇编追踪
在 main
函数设置断点,使用模拟器运行,查看汇编代码:
- 根据汇编追踪,同样的效果,
objc_sync_enter
和objc_sync_exit
函数成对出现,分别进行加锁和解锁操作。
对objc_sync_enter
设置符号断点:
- 可以发现,这个函数来自于
libobjc.A.dylib
。
底层源码分析
objc_sync_enter
打开objc4-818.2
源码,进入objc_sync_enter
函数:
- 如果
obj
存在,执行id2data
函数获取相应的SyncData
,对threadCount
、lockCount
进行递增操作。; - 否则,执行
objc_sync_nil
函数;
进入objc_sync_nil
函数:
找到BREAKPOINT_FUNCTION
的宏定义:
实际上objc_sync_nil
中的代码,相当于传入将void objc_sync_nil(void)
传入宏,等同于以下代码:
void objc_sync_nil(void) { asm(""); }
- 相当于无效代码,不会进行加锁操作
查看objc_sync_nil
的汇编代码:
libobjc.A.dylib`objc_sync_nil: -> 0x7fff2018a7a3 <+0>: ret
- 什么都不处理,直接返回
所以,使用@synchronized
时,传入nil
,相当于无效代码,不会进行加锁操作。
objc_sync_exit
:
- 如果
obj
存在,则调用id2data
方法获取对应的SyncData
,对threadCount
、lockCount
进行递减操作。 - 如果
obj
为nil
,什么也不做。
由此可见,objc_sync_enter
和objc_sync_exit
中的核心代码都是id2data
函数,参数传入的ACQUIRE
和RELEASE
有所区别,但最终都获取到一个SyncData
对象。
-
在
objc_sync_enter
中,对SyncData
对象中的mutex
,调用lock
进行加锁; -
在
objc_sync_exit
中,对SyncData
对象中的mutex
,调用tryUnlock
进行解锁。
SyncData
结构
找到SyncData
的结构定义
SyncData
结构,属于单向链表
nextData
指向下一条数据DisguisedPtr<objc_object>
用于封装类型threadCount
记录多线程操作数recursive_mutex_t
递归锁,可以递归使用,但不支持多线程递归
所以,通过SyncData
的结构不难看出,@synchronized
为递归互斥锁,支持多线程递归使用,比recursive_mutex_t
更加强大。
id2data
进入id2data
函数:
static SyncData* id2data(id object, enum usage why)
{
//1、传入object,从哈希表中获取数据
//传入object,从哈希表中获取lock,用于保证分配SyncData代码的线程安全
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
//传入object,从哈希表中获取SyncData的地址,等同于SyncList
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
bool fastCacheOccupied = NO;
//2、在当前线程的tls(线程局部存储)中寻找
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
//SyncData中的对象和传入的对象相同
if (data->object == object) {
// Found a match in fast cache.
//可以进入到这里,应该是同一线程中对同一对象,进行嵌套@synchronized
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
switch(why) {
case ACQUIRE: {
//锁的次数+1
lockCount++;
//存储到tls中
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE:
//锁的次数-1
lockCount--;
//存储到tls中
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
//删除tls线程局部存储
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
//对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
// Check per-thread cache of already-owned locks for matching object
//3、tls中未找到,在各自线程的缓存中查找
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int i;
//遍历缓存
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[i];
//item中的对象和传入的对象不一致,跳过
if (item->data->object != object) continue;
// Found a match.
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE:
//锁的次数+1
item->lockCount++;
break;
case RELEASE:
//锁的次数-1
item->lockCount--;
if (item->lockCount == 0) {
// remove from per-thread cache
//从缓存中删除
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
//对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
// Thread cache didn't find anything.
// Walk in-use list looking for matching object
// Spinlock prevents multiple threads from creating multiple
// locks for the same new object.
// We could keep the nodes in some hash table if we find that there are
// more than 20 or so distinct locks active, but we don't do that now.
//加锁,保证下面分配SyncData代码的线程安全
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
//4、遍历SyncList,如果无法遍历,证明当前object第一次进入,需要分配新的SyncData
for (p = *listp; p != NULL; p = p->nextData) {
//遍历如果链表中存在SyncData的object和传入的object相等
if ( p->object == object ) {
//将p赋值给result
result = p;
//对threadCount进行+1
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
//跳转至done
goto done;
}
//找到一个未使用的SyncData
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object
//未找到与对象关联的SyncData,如果当前非ACQUIRE逻辑,直接进入done
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
//从SyncList中找到未使用的SyncData,进行覆盖
if ( firstUnused != NULL ) {
//赋值给result
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
//跳转至done
goto done;
}
}
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
//5、分配一个新的SyncData并添加到SyncList中
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
//使用单链表头插法,新增节点总是插在头部
result->nextData = *listp;
*listp = result;
done:
//解锁,保证上面分配SyncData代码的线程安全
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
//一些错误处理,应该只有ACQUIRE时,产生新SyncData时进入这里
//所有的RELEASE和CHECK和递归ACQUIRE,都应该由上面的线程缓存处理
if (why == RELEASE) {
// Probably some thread is incorrectly exiting
// while the object is held by another thread.
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");
//6、保存到tls线程或者缓存中
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) {
//保存到当前线程的tls中
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
{
// Save in thread cache
//tls还在占用,保存到缓存
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
-
【第一步】传入
object
,从哈希表中获取数据-
传入
object
,从哈希表中获取lock
,用于保证分配SyncData
代码的线程安全 -
传入
object
,从哈希表中获取SyncData
的地址,等同于SyncList
-
-
【第二步】在当前线程的
tls
(线程局部存储)中寻找-
从
tls
中获取SyncData
-
如果存在,对比
SyncData
中的对象和传入的对象是否相同-
相同,根据传入的类型,对
lockCount
进行++
或--
,更新tls
。如果是RELEASE
操作,对SyncData
对象的threadCount
进行-1
,因为当前线程中的对象已经解锁。完成以上逻辑,直接返回result
-
不同,进入【第三步】
-
-
不存在,进入【第三步】
-
-
【第三步】
tls
中未找到,在各自线程的缓存中查找-
调用
fetch_cache
函数,获取SyncCache
-
如果存在,遍历缓存,对比
item
中的对象和传入的对象是否相同-
相同,根据传入的类型,对
lockCount
进行++
或--
,更新缓存。如果是RELEASE
操作,对SyncData
对象的threadCount
进行-1
,因为当前线程中的对象已经解锁。完成以上逻辑,直接返回result
-
遍历结束,未找到相同对象,进入【第四步】
-
-
不存在缓存,进入【第四步】
-
-
【第四步】遍历
SyncList
找到SyncData
,相当于在所有线程中寻址-
对比
SyncData
中的对象和传入的对象是否相同,同时在遍历过程中,找到未使用的SyncData
-
相同,对对
threadCount
进行+1
,进入【第六步】 -
遍历结束,未找到相同对象,查看未使用的
SyncData
是否存在-
存在,将未使用的
SyncData
中的object
覆盖,threadCount
重置为1
,进入【第六步】 -
不存在,需要分配新的
SyncData
,进入【第五步】
-
-
-
如果无法遍历,证明当前
object
第一次进入,需要分配新的SyncData
,进入【第五步】
-
-
【第五步】分配一个新的
SyncData
并添加到SyncList
中-
对
object
赋值,threadCount
初始化为1
-
使用单链表头插法,新增节点总是插在头部
-
进入【第七步】
-
-
【第六步】将
SyncData
保存到tls
线程或者缓存中-
判断
fastCacheOccupied
,如果tls
中存在SyncData
,fastCacheOccupied
为真-
如果为真,说明
tls
还在占用,保存到缓存 -
如果为假,保存到当前线程的
tls
中
-
-
通过源码分析,同线程中的lockCount
,表示@synchronized
可递归使用。而SyncData
中的threadCount
,表示@synchronized
可在多线程中使用。
哈希表的结构
lock
和data
都取自sDataLists
,类型为StripedMap
,使用static
修饰,系统中只存在一份。
来到StripedMap
的定义
StripedMap
是哈希表结构,真机预留8
个空间,而模拟器上预留64
个空间。所以之前测试锁的性能,使用真机和模拟器运行,结果略有不同。
存储到链表的情况,当不同对象生成相同哈希,也就是出现哈希冲突的情况,会直接存储在链表中
另一种情况,以模拟器为例,当StripedMap
中64
个位置都插满后,并没有扩容操作。此时出现新对象,一定会出现哈希冲突,这时会将SyncData
插入到链表中。
从代码逻辑上看,会进入【第四步】遍历SyncList
,但因为SyncData
中的对象和传入的对象不同,会在链表中找未使用的SyncData
覆盖。如果找不到未使用的SyncData
,会分配一个新的SyncData
并添加到SyncList
中:
TLS
线程相关解释
线程局部存储(Thread Local Storage,TLS
):是操作系统为线程单独提供的私有空间,通常只有有限的容量
Linux
系统下通过pthread
库中的API
使用:
-
pthread_key_create()
-
pthread_getspecific()
-
pthread_setspecific()
-
pthread_key_delete()
注意事项
-
@synchronized
为递归互斥锁,lockCount
表示可递归使用,threadCount
表示可在多线程中使用; -
使用
@synchronized
时,不能传入nil
,使用nil
锁的功能无法生效; -
在日常开发中,经常会传入
self
,它的好处可以保证生命周期同步,对象不会提前释放; -
不能使用非
OC
对象作为加锁对象,因为其object
的参数为id
类型; -
底层的缓存和链表都使用循环遍历查找,所以性能偏低。但开发中使用方便简单,并且不用解锁,所以使用频率较高。
pthread_mutex
在Posix Thread
中定义有⼀套专⻔⽤于线程同步的mutex
函数。
mutex
:⽤于保证在任何时刻,都只能有⼀个线程访问该对象。当获取锁操作失败时,线程会进⼊睡眠,等待锁释放时被唤醒。
我们看下 api
:
//导入头文件
#import <pthread/pthread.h>
//锁的声明
pthread_mutex_t _lock;
//锁的初始化
pthread_mutex_init(&_lock, NULL);
//加锁
pthread_mutex_lock(&_lock);
//解锁
pthread_mutex_unlock(&_lock);
//锁的释放
pthread_mutex_destroy(&_lock);
NSLock
NSLock
的底层对pthread_mutex
进行封装,同样是一把互斥锁。但它属于非递归互斥锁,所有不能进行递归使用。
我们看下 api
:
//锁的初始化
NSLock *lock = [[NSLock alloc] init];
//加锁
[lock lock];
//解锁
[lock unlock];
源码分析
NSLock
的源码在Foundation
框架中:
由于OC
的Foundation
框架未能开源,我们通过Swift
的Foundation
源码代替
打开swift-corelibs-foundation-master
项目,找到NSLock
的实现
NSLock
遵循NSLocking
协议
NSLock
的初始化、销毁、加锁、解锁功能,都是对pthread_mutex
进行二次封装:
-
NSLock
的初始化方法中,封装了pthread_mutex
的初始化; -
使用
NSLock
必须调用它的init
方法; -
通过
pthread_cond_broadcast
广播,唤醒在锁中等待的所有线程。
NSLock
的不足之处
NSLock
属于非递归互斥锁,所有不能进行递归使用。
举个例子:
- (void)lg_testRecursive{
NSLock *lock = [[NSLock alloc] init];
for (int i=0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
};
[lock lock];
testMethod(10);
[lock unlock];
});
}
}
- 在异步函数的代码中,使用
NSLock
进行加锁和解锁,可以保证线程安全,从10~1
顺序输出,循环打印10
遍。
在block
方法中进行加锁,同样可以保证线程安全。但NSLock
属于非递归锁,遇到递归场景,在没有unlock
的情况下,再次执行lock
,造成死锁:
- 只输出了
10
, 后面的结果无法继续打印。
NSRecursiveLock
NSRecursiveLock
也是对pthread_mutex
进行封装,属于递归锁。它可以允许同一线程多次加锁,而不会造成死锁。
- 与
NSLock
相比,它可以在递归场景中使用; - 但它没有
@synchronized
强大,因为它不支持在多线程中递归加锁。
看下 api
的调用:
//锁的初始化
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
//加锁
[recursiveLock lock];
//解锁
[recursiveLock unlock];
源码分析
找到NSRecursiveLock
的实现,同样遵循了NSLocking
协议。NSRecursiveLock
的初始化、销毁、加锁、解锁功能,都是对pthread_mutex
进行二次封装:
NSRecursiveLock
的实现和NSLock
非常相似,它们最大的区别在于初始化,NSRecursiveLock
对pthread_mutex
设置PTHREAD_MUTEX_RECURSIVE
标示,故此NSRecursiveLock
是一把递归互斥锁。
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
NSRecursiveLock
的不足之处
NSRecursiveLock
会跟踪它被lock
的次数。每次成功的lock
都必须平衡调用unlock
操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。用个案例演示下:
- 从打印结果上看,在单一线程中正常打印出
10~1
后,多线程递归执行报错。
NSCondition
NSCondition
属于条件锁,使用的方式和信号量相似。当线程满足需求后才会继续执行,否则会阻塞线程,使其进入休眠状态。
NSCondition
的对象实际上作为⼀个锁和⼀个线程检查器:
- 锁主要为了当检测条件时保护数据源,执⾏条件引发的任务
- 线程检查器主要是根据条件决定是否继续运⾏线程,即线程是否被阻塞
查看下 api
:
//⽤于多线程同时访问、修改同⼀个数据源
//保证在同⼀时间内数据源只被访问、修改⼀次
//其他线程的命令需要在lock外等待,直到unlock,才可访问
[condition lock];
//与lock同时使⽤
[condition unlock];
//使当前线程处于等待状态
[condition wait];
//CPU发信号告诉线程不⽤等待,可以继续执⾏
[condition signal];
使用
生产消费者的模型案例:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, assign) NSUInteger ticketCount;
@property (nonatomic, strong) NSCondition *testCondition;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self lg_testConditon];
}
#pragma mark -- NSCondition
- (void)lg_testConditon{
_testCondition = [[NSCondition alloc] init];
//创建生产-消费者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
}
}
- (void)lg_producer{
//操作的多线程影响
[_testCondition lock];
self.ticketCount = self.ticketCount + 1;
NSLog(@"生产一个 现有 count %zd",self.ticketCount);
//信号
[_testCondition signal];
[_testCondition unlock];
}
- (void)lg_consumer{
//操作的多线程影响
[_testCondition lock];
while (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait];
}
//注意消费行为,要在等待条件判断之后
self.ticketCount -= 1;
NSLog(@"消费一个 还剩 count %zd",self.ticketCount);
[_testCondition unlock];
}
@end
生产者和消费者都进行了加锁处理,保证线程安全
当消费者发现库存为0
的时候,线程等待
当生产者增加库存后,发送信号。消费者收到信号,之前等待的线程继续执行
消费者判断库存为0
,使用while
循环,而不是if
判断:
-
使用
if
判断,唤醒线程后会从wait
之后的代码开始运行,但是不会重新判断if
条件,直接继续运行if
代码块之后的代码; -
使用
while
循环,也会从wait
之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while
代码块之后的代码块,成立的话继续wait
。
源码分析
找到NSCondition
的实现:
-
底层采用
pthread_mutex
+pthread_cond
实现:-
pthread_mutex
:对互斥锁进行封装; -
pthread_cond
:用来控制条件变量的执行情况。
-
-
wait
:操作会阻塞线程,使其进入休眠状态; -
signal
:操作唤醒一个正在休眠等待的线程,使其继续执行wait
之后的代码块; -
broadcast
:唤醒在锁中等待的所有线程。
NSConditionLock
NSConditionLock
是一把条件锁,⼀个线程获得锁,其他线程⼀定等待。它的内部对NSCondition
又做了一层封装,自带条件探测,能够更简单灵活的使用。
查看下 api
:
⽆条件锁
:
[conditionLock lock];
[conditionLock unlock];
-
表示
condition
期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition
) ,那它能执⾏此⾏以下代码; -
如果已经有其他线程获得锁(可能是条件锁,或者⽆条件锁),则等待,直⾄其他线程解锁。
条件锁
:
[conditionLock lockWhenCondition:A条件];
-
表示如果没有其他线程获得该锁,但是该锁内部的
condition
不等于A条件
,它依然不能获得锁,仍然等待; -
如果内部的
condition
等于A条件
,并且没有其他线程获得该锁,则进⼊代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直⾄它解锁; -
所谓的
A条件
就是整数,内部通过整数⽐较条件。
释放条件锁
[conditionLock unlockWithCondition:A条件];
- 表示释放锁,同时把内部的
condition
设置为A条件
。
条件锁
+ 超时时间
:
return = [conditionLock lockWhenCondition:A条件 beforeDate:A时间];
- 表示如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是
NO
,它没有改变锁的状态,这个函数的⽬的在于可以实现两种状态下的处理。
NSConditionLock
与 NSCondition
相比:
-
相同点:
-
都是互斥锁;
-
通过条件变量来控制加锁、释放锁,从而达到阻塞线程、唤醒线程的目的。
-
-
不同点:
-
NSCondition
是基于pthread_mutex
的封装,而NSConditionLock
是基于NSCondition
又做了一层封装; -
NSCondition
需要手动让线程进入等待状态阻塞线程、释放信号唤醒线程,而NSConditionLock
只需要外部传入一个值,就会依据这个值进行自动判断,决定阻塞线程还是唤醒线程。
-
使用
案例演示:
- (void)lg_testConditonLock {
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];
NSLog(@"线程2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"线程3");
[conditionLock unlock];
});
}
打印结果:
- 案例的执行结果,大概率为
线程3、线程2、线程1
。
NSConditionLock
初始化,设置的条件为2
。按照线程优先级顺序:
-
线程1
优先级最高,但不符合条件,代码块无法执行,进入等待状态; -
线程3
默认优先级,同时属于无条件锁,可以执行代码块; -
线程2
优先级最低,但符合条件,也可以执行代码块;
所以,大概率线程3
会在线程2
之前打印,而线程1
必须等待线程2
执行完,释放条件锁之后才能执行。
源码分析
初始化
:
NSCondition
和条件value
,作为NSConditionLock
的成员变量;- 初始化时将条件赋值
value
。
无条件锁
:
open func lock() {
let _ = lock(before: Date.distantFuture)
}
open func lock(before limit: Date) -> Bool {
_cond.lock()
while _thread != nil {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
#if os(Windows)
_thread = GetCurrentThread()
#else
_thread = pthread_self()
#endif
_cond.unlock()
return true
}
- 调用
lock
方法,底层调用的是lock(before limit: Date)
方法,无条件判断,可直接执行。
释放无条件锁
:
open func unlock() {
_cond.lock()
#if os(Windows)
_thread = INVALID_HANDLE_VALUE
#else
_thread = nil
#endif
_cond.broadcast()
_cond.unlock()
}
- 调用
unlock
方法,实际上处理的是NSCondition
。
条件锁
:
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()
while _thread != nil || _value != condition {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
#if os(Windows)
_thread = GetCurrentThread()
#else
_thread = pthread_self()
#endif
_cond.unlock()
return true
}
-
内部进行
lock
和unlock
处理,保证线程安全; -
如果条件不一致,调用
wait
方法进入等待状态; -
否则,调用
pthread_self
方法。
释放条件锁
:
open func unlock(withCondition condition: Int) {
_cond.lock()
#if os(Windows)
_thread = INVALID_HANDLE_VALUE
#else
_thread = nil
#endif
_value = condition
_cond.broadcast()
_cond.unlock()
}
-
内部进行
lock
和unlock
处理,保证线程安全; -
更新条件
value
; -
调用
broadcast
方法,进行广播,唤醒在锁中等待的所有线程。
汇编分析
initWithCondition
设置-[NSConditionLock initWithCondition:]
符号断点,真机运行项目:
- 面对复杂的汇编代码,我们可以对所有的跳转代码设置断点,例如:
bl
、b
等指令,然后读取x0
、x1
寄存器查看消息接收者和方法编号,遇到ret
指令,通过x0
查看返回值。
执行bl 0x188fa9e34
指令:
(lldb) register read x0
x0 = 0x000000016f7b99c8
(lldb) register read x1
x1 = 0x00000001d0748898
(lldb) po 0x000000016f7b99c8
6165338568
(lldb) po (SEL)0x00000001d0748898
"init"
- 调用一个未知对象的
init
方法。
执行bl 0x1893db4fc
指令:
(lldb) register read x0
x0 = 0x0000000283e72670
(lldb) register read x1
x1 = 0x00000001d0748898
(lldb) po 0x0000000283e72670
<NSConditionLock: 0x283e72670>{condition = 0, name = nil}
(lldb) po (SEL)0x00000001d0748898
"init"
- 调用
NSConditionLock
对象的init
方法。
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000283e72670
(lldb) register read x1
x1 = 0x00000001cffedc93
(lldb) po 0x0000000283e72670
<NSConditionLock: 0x283e72670>{condition = 0, name = nil}
(lldb) po (SEL)0x00000001cffedc93
"zone"
- 调用
NSConditionLock
对象的zone
方法,开辟内存空间;
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x00000001de1d0e20 (void *)0x00000001de1d0e48: NSCondition
(lldb) register read x1
x1 = 0x00000001d0c3d418
(lldb) po (SEL)0x00000001d0c3d418
"allocWithZone:"
(lldb) register read x2
x2 = 0x00000001ded98000 libsystem_malloc.dylib`virtual_default_zone
- 调用
NSCondition
类对象的allocWithZone:
方法; - 传入参数:
virtual_default_zone
。
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000280220510
(lldb) register read x1
x1 = 0x00000001d0748898
(lldb) po 0x0000000280220510
<NSCondition: 0x280220510>{name = nil}
(lldb) po (SEL)0x00000001d0748898
"init"
- 调用
NSCondition
对象的init
方法。
- 比较
x8
、x9
寄存器; b.ne
:比较结果是不等于(not equal to
),执行标号处指令,否则继续执行。
输出x8
、x9
寄存器:
(lldb) register read x9
x9 = 0x933ba4ad46660065
(lldb) register read x8
x8 = 0x933ba4ad46660065
- 相等,继续执行。
执行ret
指令,输出x0
寄存器,查看返回值
(lldb) register read x0
x0 = 0x0000000283e72670
(lldb) po 0x0000000283e72670
<NSConditionLock: 0x283e72670>{condition = 2, name = nil
}
- 返回
NSConditionLock
实例对象。
使用x/8g
命令,查看实例对象的内存结构:
(lldb) x/8g 0x283e72670
0x283e72670: 0x000021a1de1d0ec1 0x0000000000000000
0x283e72680: 0x0000000280220510 0x0000000000000000
0x283e72690: 0x0000000000000002 0x0000000000000000
0x283e726a0: 0x0000a817e1de26a0 0x00000000006000cc
(lldb) po 0x0000000280220510
<NSCondition: 0x280220510>{name = nil
}
NSCondition
和初始化设置的默认条件,都作为成员变量保存在NSConditionLock
的实例对象中;- 设置的默认条件:
0x0000000000000002
,即:初始化时传入的2
。
lockWhenCondition:
设置-[NSConditionLock lockWhenCondition:]
符号断点:
输出x0
、x1
、x2
寄存器:
(lldb) register read x0
x0 = 0x0000000283fcbb40
(lldb) register read x1
x1 = 0x00000001d0543629
(lldb) register read x2
x2 = 0x0000000000000001
(lldb) po 0x0000000283fcbb40
<NSConditionLock: 0x283fcbb40>{condition = 2, name = nil}
(lldb) po (SEL)0x00000001d0543629
"lockWhenCondition:"
- 执行
线程1
的代码; x2
:传入的条件值为1
。
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x00000001de1c7030 (void *)0x00000001de1c7058: NSDate
(lldb) register read x1
x1 = 0x00000001d0f8b6aa
(lldb) po (SEL)0x00000001d0f8b6aa
"distantFuture"
- 调用
NSDate
的distantFuture
方法。
执行b 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x00000002813fc360
(lldb) register read x1
x1 = 0x00000001d070122e
(lldb) po 0x00000002813fc360
<NSConditionLock: 0x2813fc360>{condition = 2, name = nil}
(lldb) po (SEL)0x00000001d070122e
"lockWhenCondition:beforeDate:"
(lldb) register read x2
x2 = 0x0000000000000001
(lldb) register read x3
x3 = 0x00000001d64d7168 CoreFoundation`_NSConstantDateDistantFuture
-
调用
NSConditionLock
对象的lockWhenCondition:beforeDate:
方法; -
x2
:传入的条件值1
; -
x3
:上一个bl
,调用distantFuture
方法得到的返回值。
lockWhenCondition: beforeDate:
设置-[NSConditionLock lockWhenCondition:beforeDate:]
符号断点:
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000280220510
(lldb) register read x1
x1 = 0x00000001d0195618
(lldb) po 0x0000000280220510
<NSCondition: 0x280220510>{name = nil}
(lldb) po (SEL)0x00000001d0195618
"lock"
- 调用
NSCondition
对象的lock
方法。
- 比较
x8
、x21
寄存器; b.eq
:比较结果是等于(equal to
),执行标号处指令,否则继续执行。
输出x8
、x21
寄存器:
(lldb) register read x8
x8 = 0x0000000000000002
(lldb) register read x21
x21 = 0x0000000000000001
- 不相等,继续执行。
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000281ad5050
(lldb) register read x1
x1 = 0x00000001d099aac3
(lldb) po 0x0000000281ad5050
<NSCondition: 0x281ad5050>{name = nil}
(lldb) po (SEL)0x00000001d099aac3
"waitUntilDate:"
- 调用
NSCondition
对象的waitUntilDate:
方法; - 线程进入等待状态。
程序继续执行,此时会切换线程,跳转到线程2
的lockWhenCondition:
方法:
x2
:传入的条件值为2
。
程序继续执行,进入线程2
的lockWhenCondition:beforeDate:
方法:
(lldb) register read x0
x0 = 0x0000000283e72670
(lldb) register read x1
x1 = 0x00000001d070122e
(lldb) po 0x0000000283e72670
<NSConditionLock: 0x283e72670>{condition = 2, name = nil}
(lldb) po (SEL)0x00000001d070122e
"lockWhenCondition:beforeDate:"
(lldb) register read x2
x2 = 0x0000000000000002
(lldb) register read x3
x3 = 0x00000001d64d7168 CoreFoundation`_NSConstantDateDistantFuture
- 和之前调用
线程1
的区别,传入的条件值为2
。
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000280220510
(lldb) register read x1
x1 = 0x00000001d0195618
(lldb) po 0x0000000280220510
<NSCondition: 0x280220510>{name = nil}
(lldb) po (SEL)0x00000001d0195618
"lock"
- 调用
NSCondition
对象的lock
方法。
- 比较
x8
、x21
寄存器; b.eq
:比较结果是等于(equal to
),执行标号处指令,否则继续执行。
输出x8
、x21
寄存器:
(lldb) register read x8
x8 = 0x0000000000000002
(lldb) register read x21
x21 = 0x0000000000000002
- 相等,执行标号处指令:
执行bl 0x188faa140
指令:
(lldb) register read x0
x1 = 0x0000000000000001
- 只能看出
x0
寄存器的值为1
,具体执行的代码未知。
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000280220510
(lldb) register read x1
x1 = 0x00000001d03bca18
(lldb) po 0x0000000280220510
<NSCondition: 0x280220510>{name = nil}
(lldb) po (SEL)0x00000001d03bca18
"unlock"
- 调用
NSCondition
对象的unlock
方法。
执行ret
指令,输出x0
寄存器,查看返回值:
(lldb) register read x0
x0 = 0x0000000000000001
- 返回值为
1
,表示条件符合,代码块执行完成。
如果条件不符合,线程进入等待状态,因为超时导致代码块未执行,会进行解锁,然后返回0
:
unlockWithCondition:
设置-[NSConditionLock unlockWithCondition:]
符号断点:
进入线程2
的unlockWithCondition:
方法:
(lldb) register read x2
x2 = 0x0000000000000001
- 传入的条件值为
1
。
执行bl 0x1893db4fc
指令:
(lldb) register read x0
x0 = 0x0000000282688330
(lldb) register read x1
x1 = 0x00000001d025a602
(lldb) po 0x0000000282688330
<NSConditionLock: 0x282688330>{condition = 2, name = nil}
(lldb) po (SEL)0x00000001d025a602
"unlockWithCondition:"
- 调用
NSConditionLock
对象的unlockWithCondition:
方法。
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000281ad5050
(lldb) register read x1
x1 = 0x00000001d0195618
(lldb) po 0x0000000281ad5050
<NSCondition: 0x281ad5050>{name = nil}
(lldb) po (SEL)0x00000001d0195618
"lock"
- 调用
NSCondition
对象的lock
方法。
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000281ad5050
(lldb) register read x1
x1 = 0x00000001d004280d
(lldb) po 0x0000000281ad5050
<NSCondition: 0x281ad5050>{name = nil}
(lldb) po (SEL)0x00000001d004280d
"broadcast"
- 调用
NSCondition
对象的broadcast
方法,进行广播,唤醒在锁中等待的所有线程。
执行b 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000281ad5050
(lldb) register read x1
x1 = 0x00000001d03bca18
(lldb) po 0x0000000281ad5050
<NSCondition: 0x281ad5050>{name = nil}
(lldb) po (SEL)0x00000001d03bca18
"unlock"
- 调用
NSCondition
对象的unlock
方法。
使用b
指令,表示义无反顾的跳转,不再返回。此方法执行完,线程2
的代码执行结束。
但是,在线程2
中,调用NSCondition
对象的broadcast
方法,触发广播操作。此时,处于等待状态的线程1
将被唤醒,继续线程1
的代码执行。
唤醒线程1
程序继续执行,此时会切换线程,跳转到线程1
的lockWhenCondition:
方法
此时线程1
解除等待状态,继续执行代码:
- 比较
x8
、x21
寄存器; b.eq
:比较结果是等于(equal to
),执行标号处指令,否则继续执行。
输出x8
、x21
寄存器:
(lldb) register read x8
x8 = 0x0000000000000001
(lldb) register read x21
x21 = 0x0000000000000001
- 相等,执行标号处指令。
执行bl 0x188faa140
指令:
(lldb) register read x0
x1 = 0x0000000000000001
- 只能看出
x0
寄存器的值为1
,具体执行的代码未知。
执行bl 0x188fa9e28
指令:
(lldb) register read x0
x0 = 0x0000000280220510
(lldb) register read x1
x1 = 0x00000001d03bca18
(lldb) po 0x0000000280220510
<NSCondition: 0x280220510>{name = nil}
(lldb) po (SEL)0x00000001d03bca18
"unlock"
- 调用
NSCondition
对象的unlock
方法。
执行ret
指令,输出x0
寄存器,查看返回值:
(lldb) register read x0
x0 = 0x0000000000000001
- 返回值为
1
。
之后会进入线程1
的unlockWithCondition:
方法,释放条件锁。
自旋锁
atomic
atomic
是属性的修饰,不是锁。
它在底层自带一把自旋锁,只是能保证数据的完整性,单一线程安全,不能保证多线程安全。效率低,不推荐使用。
案例演示:
在线程A
中,循环遍历的索引为偶数,数组中的元素一定存在三个;
在线程B
中,如果数组中的元素>= 2
,获取第一个元素;
如果这两个逻辑在同一线程中执行,一定不会报错。但由于多线程,atomic
无法保证多线程安全,所以程序还是会崩溃。
OSSpinLock
OSSpinLock
是一把自旋锁,由于安全性问题,iOS10
之后被os_unfair_lock
替代。使用OSSpinLock
,会让线程处于忙等待状态。目前OSSpinLock
还可以使用,但不推荐。
案例演示:
#import "ViewController.h"
#import <libkern/OSAtomic.h>
#import <os/lock.h>
@interface ViewController (){
OSSpinLock _spinLock;
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self lg_testOSSPinLock];
}
#pragma mark -- OSSpinLock - (void)lg_testOSSPinLock {
_spinLock = OS_SPINLOCK_INIT;
// 线程1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"线程1 准备上锁,currentThread:%@",[NSThread currentThread]);
OSSpinLockLock(&_spinLock);
NSLog(@"线程1");
sleep(3);
OSSpinLockUnlock(&_spinLock);
NSLog(@"线程1 解锁完成");
});
// 线程2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"线程2 准备上锁,currentThread:%@",[NSThread currentThread]);
OSSpinLockLock(&_spinLock);
NSLog(@"线程2");
sleep(3);
OSSpinLockUnlock(&_spinLock);
NSLog(@"线程2 解锁完成");
});
}
@end
os_unfair_lock
os_unfair_lock
是一把自旋锁,使用os_unfair_lock
,会让线程处于休眠状态.
查看 api
:
//导入头文件
#import <libkern/OSAtomic.h>
#import <os/lock.h>
//初始化
os_unfair_lock _unfairLock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&_unfairLock);
//解锁
os_unfair_lock_unlock(&_unfairLock);
读写锁
通常,当读写锁处于读模式锁住状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁⻓期占⽤,⽽等待的写模式锁请求⻓期阻塞。
读写锁适合于对数据结构的读次数⽐写次数多得多的情况,因为读模式锁定时可以共享,以写模式锁住时意味着独占,所以读写锁⼜叫共享/独占锁
。
读写锁要实现以下几点:
-
多读单写
-
读和写互斥
-
写操作之间互斥
-
不能阻塞正常的任务执行
读写锁的两种实现方式:
-
使用
pthread
实现 -
使用
GCD
实现
使用pthread
实现
//导入头文件
#include <pthread.h>
//初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)
//释放
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
- 成功返回
0
, 出错返回错误编号; - 同互斥量以上,在释放读写锁占⽤的内存之前,需要先通过
pthread_rwlock_destroy
对读写锁进⾏清理⼯作,释放由init
分配的资源。
//读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//释放锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
-
成功返回
0
, 出错返回错误编号; -
三个函数分别实现获取读锁,获取写锁和释放锁的操作;
-
获取锁的两个函数是阻塞操作。
⾮阻塞的获取锁操作:
//读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
//写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
- 成功返回
0
, 出错返回错误编号。
使用GCD
实现
使用GCD
创建自定义并发队列:
-
通过同步函数实行读操作
-
通过异步栅栏函数实现写操作
将同步函数和异步栅栏函数加入同一队列,可以满足读写互斥,写操作之间的互斥。并且异步栅栏函数只会阻塞自身队列中的任务,不会影响其他任务的执行。
声明
:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) NSMutableDictionary *dic;
@property (nonatomic,strong) dispatch_queue_t queue;
@end
初始化
:
- (void)viewDidLoad {
[super viewDidLoad];
_dic = [NSMutableDictionary dictionary];
_queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
}
读操作
:
- (NSString *)safrGetter:(NSString *)strKey{
__block NSString* strValue;
dispatch_sync(self.queue, ^{
strValue = [self.dic objectForKey:strKey];
NSLog(@"safrGetter:%@,%@,%@",strKey,strValue,[NSThread currentThread]);
});
return strValue;
}
写操作
:
- (void)safeSetter:(NSString *)strKey strValue:(NSString *)strValue {
dispatch_barrier_async(self.queue, ^{
[self.dic setObject:strValue forKey:strKey];
NSLog(@"safeSetter:%@,%@,%@",strKey,strValue,[NSThread currentThread]);
});
}
测试代码
:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self test];
}
-(void)test {
for (int intIndex=0; intIndex<50; intIndex++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self safeSetter:[NSString stringWithFormat:@"key_%i",intIndex] strValue:[NSString stringWithFormat:@"value_%i",intIndex]];
[self safrGetter:[NSString stringWithFormat:@"key_%i",intIndex]];
});
}
}