本文首发于个人博客
前言
iOS中有很多锁,那么平时使用过程中到底怎么使用呢?本文分享13种加锁方案。本文较长总共一万字。文中代码在github上。
OSSpinLock
自旋锁os_unfair_lock
互斥锁pthread_mutex
递归锁pthread_mutex
条件锁dispatch_semaphore
信号量dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
dispatch_barrier_async
栅栏dispatch_group
调度组
性能对比:借用ibireme大神的一张图片
可以看到除了 OSSpinLock
外,dispatch_semaphore
和 pthread_mutex
性能是最高的。现在苹果在新系统中已经优化了 pthread_mutex
的性能,所以它看上去和 OSSpinLock
差距并没有那么大了
GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍
虽然GNUstep不是苹果官方源码,但还是具有一定的参考价值
自旋锁
wikipedia中关于自旋锁的描述
自旋锁是计算机科学用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。
自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。因此操作系统的实现在很多地方往往用自旋锁。Windows操作系统提供的轻型读写锁(SRW Lock)内部就用了自旋锁。显然,单核CPU不适于使用自旋锁,这里的单核CPU指的是单核单线程的CPU,因为,在同一时间只有一个线程是处在运行状态,假设运行线程A发现无法获取锁,只能等待解锁,但因为A自身不挂起,所以那个持有锁的线程B没有办法进入运行状态,只能等到操作系统分给A的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。
互斥锁
wikipedia中关于互斥锁的描述
互斥锁(英语:Mutual exclusion,缩写 Mutex)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区域(critical section)达成。临界区域指的是一块对公共资源进行访问的代码,并非一种机制或是算法。一个程序、进程、线程可以拥有多个临界区域,但是并不一定会应用互斥锁。
需要此机制的资源的例子有:旗标、队列、计数器、中断处理程序等用于在多条并行运行的代码间传递数据、同步状态等的资源。维护这些资源的同步、一致和完整是很困难的,因为一条线程可能在任何一个时刻被暂停(休眠)或者恢复(唤醒)。
例如:一段代码(甲)正在分步修改一块数据。这时,另一条线程(乙)由于一些原因被唤醒。如果乙此时去读取甲正在修改的数据,而甲碰巧还没有完成整个修改过程,这个时候这块数据的状态就处在极大的不确定状态中,读取到的数据当然也是有问题的。更严重的情况是乙也往这块地方写数据,这样的一来,后果将变得不可收拾。因此,多个线程间共享的数据必须被保护。达到这个目的的方法,就是确保同一时间只有一个临界区域处于运行状态,而其他的临界区域,无论是读是写,都必须被挂起并且不能获得运行机会。
读写锁
wikipedia中关于互斥锁的描述
读写锁是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁。多读者锁,“push lock”) 用于解决读写问题。读操作可并发重入,写操作是互斥的。
读写锁通常用互斥锁、条件变量、信号量实现。
读写锁可以有不同的操作模式优先级:
读操作优先:允许最大并发,但写操作可能饿死。
写操作优先:一旦所有已经开始的读操作完成,等待的写操作立即获得锁。内部实现需要两把互斥锁。
未指定优先级
信号量
wikipedia中关于信号量的描述
信号量(英语:semaphore)又称为信号标,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态.
semaphore对象适用于控制一个仅支持有限个用户的共享资源,是一种不需要使用忙碌等待(busy waiting)的方法。
信号量的概念是由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)发明的,广泛的应用于不同的操作系统中。在系统中,给予每一个进程一个信号量,代表每个进程当前的状态,未得到控制权的进程会在特定地方被强迫停下来,等待可以继续进行的信号到来。如果信号量是一个任意的整数,通常被称为计数信号量(Counting semaphore),或一般信号量(general semaphore);如果信号量只有二进制的0或1,称为二进制信号量(binary semaphore)。在linux系统中,二进制信号量(binary semaphore)又称互斥锁(Mutex)
场景
使用经典的存钱-取钱案例。假设我们账号里面有100元,每次存钱都存10元,每次取钱都取20元。存5次,取5次。那么就是应该最终剩下50元才对。如果我们把存在和取钱在不同的线程中访问的时候,如果不加锁,就很可能导致问题。
/** 存钱、取钱演示 */ - (void)moneyTest { self.money = 100; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{for (int i = 0; i < 5; i++) { [self __saveMoney]; } }); dispatch_async(queue, ^{for (int i = 0; i < 5; i++) { [self __drawMoney]; } }); } /** 存钱 */ - (void)__saveMoney { int oldMoney = self.money; sleep(.2); oldMoney += 10; self.money = oldMoney; NSLog(@"存10元,还剩%d元 - %@", oldMoney, [NSThread currentThread]); } /** 取钱 */ - (void)__drawMoney { int oldMoney = self.money; sleep(.2); oldMoney -= 20; self.money = oldMoney; NSLog(@"取20元,还剩%d元 - %@", oldMoney, [NSThread currentThread]); }复制代码
输出结果为
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
从结果上来看,明显不是预期的那样
这是因为,正常情况下,来存钱取消,存10元之后,还剩下110元,然后取钱20元,剩余90元没问题。但是我们是不同线程同时操作的时候,可能导致的情况是,正在存钱的是,来取钱了。也就是10元还没存进去,就去取钱。取钱之后先去获取当前的钱数,因为10元正在存呢,还没存完,取钱的时候,当前是100元,然后取出20元的过程中,刚才的10元存进去了,然后20元也取出来了。给出结果是100-20 = 80 元,然后实际上应该 100+10-20 = 90 元。这样的话,就导致了数据的紊乱。
如何解决:
解决这种问题,就需要线程锁了。当存钱的时候,先去加锁,然后存完了,再放开锁。取钱也是一样,这样就保证数据的一致性。
OSSpinLock
自旋锁
OSSpinLock
叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源目前已经不再安全,可能会出现优先级反转问题
需要导入头文件
#import
使用
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
YZOSSpinLock
继承YZBaseLock
,在每次存钱,取钱之前进行加锁,在每次存钱,取钱之后进行解锁。
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
输出结果为
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
由输出可知,能保证线程安全,数据没有错乱。但是OSSpinLock
已经不再安全了。
汇编跟踪
在加锁的地方打断点,第二次进来的是,已经加锁了,这时候看加锁的汇编代码
Debug->Debug Worlflow->Always Show Disassembly
为什么OSSpinLock
不再安全
关于为什么OSSpinLock
不再安全可以参考这篇文章不再安全的 OSSpinLock
这里摘要主要内容
如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock,这就是优先级反转。这并不只是理论上的问题,开发者已经遇到很多次这个问题,于是苹果工程师停用了 OSSpinLock。
结论
除非开发者能保证访问锁的线程全部都处于同一优先级,否则 iOS 系统中所有类型的自旋锁都不能再使用了。
os_unfair_lock
互斥锁
os_unfair_lock
用于取代不安全的OSSpinLock ,从iOS10开始才支持 从底层调用看,等待os_unfair_lock
锁的线程会处于休眠状态,并非忙等
需要导入头文件#import
使用
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
YZUnfairLock
继承YZBaseLock
,在每次存钱,取钱之前进行加锁,在每次存钱,取钱之后进行解锁。
#import "YZUnfairLock.h"#import@interface YZUnfairLock() @property (nonatomic ,assign) os_unfair_lock moneyLock; @end @implementation YZUnfairLock - (instancetype)init { if (self = [super init]) { self.moneyLock = OS_UNFAIR_LOCK_INIT; }return self; } - (void)__saveMoney { os_unfair_lock_lock(&_moneyLock); [super __saveMoney]; os_unfair_lock_unlock(&_moneyLock); } - (void)__drawMoney { os_unfair_lock_lock(&_moneyLock); [super __drawMoney]; os_unfair_lock_unlock(&_moneyLock); } @end复制代码
汇编跟踪
在加锁的地方打断点,第二次进来的是,已经加锁了,这时候看加锁的汇编代码
Debug->Debug Worlflow->Always Show Disassembly
断点跟踪进去,会发现最终到syscall
的时候,断点失效了。这是因为syscall调用了系统内核的函数,使得线程进入休眠状态,不再占用CPU资源。所以可以看出os_unfair_lock
是互斥锁。
pthread_mutex
互斥锁
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
需要导入头文件#import
使用
// 初始化属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); // 初始化锁 pthread_mutex_init(mutex, &attr); // 销毁属性 pthread_mutexattr_destroy(&attr);复制代码
其中锁的类型有四种
#define PTHREAD_MUTEX_NORMAL 0 //一般的锁#define PTHREAD_MUTEX_ERRORCHECK 1 // 错误检查#define PTHREAD_MUTEX_RECURSIVE 2 //递归锁#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL //默认复制代码
当类型是PTHREAD_MUTEX_DEFAULT
的时候,相当于null
例如上面的使用可以直接等价于
pthread_mutex_init(mutex, NULL); //传空,相当于PTHREAD_MUTEX_DEFAULT复制代码
YZMutexLock
继承YZBaseLock
,在每次存钱,取钱之前进行加锁,在每次存钱,取钱之后进行解锁。
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
0
看到输出也是没问题的。线程是安全的。
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
1
pthread_mutex
递归锁
mutex除了有”互斥锁”,还有递归锁
需要导入头文件#import
使用
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
2
其中锁的类型有四种
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
3
eg:
YZMutexRecursiveLock
继承YZBaseLock
,otherTest
里面进行递归加锁
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
4
调用的时候
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
5
输出结果为:
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
6
由结果可知,连续加锁五次,是因为每次都递归加锁。然后解锁时候,层层解锁。
pthread_mutex
条件锁
mutex除了有"互斥锁","递归锁",还有递归锁
需要导入头文件#import
使用
生产者消费者
为了演示条件锁的作用,就用生产者消费者来展示效果,关于生产者消费者的设计模式,可以看我之前的文章iOS设计模式之(二)生产者-消费者,那篇文章中用的是信号量实现的。这篇文章用pthread_mutex
条件锁来实现。
代码
有三个属性
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
7
初始化
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
8
eg:
YZMutexCondLock
继承YZBaseLock
,otherTest
里面进行测试
iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩120元 -{number = 4, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩110元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩100元 -{number = 3, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩90元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩100元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[21005:249636] 存10元,还剩90元 -{number = 4, name = (null)} iOS-LockDemo[21005:249637] 取20元,还剩70元 -{number = 3, name = (null)}复制代码
9
调用的时候
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
0
输出结果为:
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
1
由结果可知,打印完__remove - 等待
之后,等待了一秒钟,添加元素之后,放开锁,才去删除元素。
NSLock锁
NSLock
是对mutex
普通锁的封装
api
NSLocking
协议有加锁lock
和解锁unlock
,
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
2
NSLock准守这个协议,锁可以直接使用,另外,还有tryLock
和lockBeforeDate
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
3
使用
YZNSLock
继承YZBaseLock
,在每次存钱,取钱之前进行加锁,在每次存钱,取钱之后进行解锁。
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
4
输出结果为
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
5
由输出可知,能保证线程安全,数据没有错乱。
NSLock
是对mutex
普通锁的封装
如果想证明NSLock
是对mutex
普通锁的封装有两种方式
汇编分析
汇编分析来说,可以打断点跟进去,最终会发现调用了
mutex
,因为,lock是调用的msgSend,汇编代码比较复杂,读者有兴趣可自行验证。
GNUstep源码的NSLock.m中如下代码
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
6
NSRecursiveLock锁
NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致
api
NSLocking
协议有加锁lock
和解锁unlock
,
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
7
NSRecursiveLock准守这个协议,可以直接使用,另外,还有tryLock
和lockBeforeDate
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
8
使用
YZNSRecursiveLock
继承YZBaseLock
,在每次存钱,取钱之前进行加锁,在每次存钱,取钱之后进行解锁。
// 初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = OSSpinLockTry(lock); //加锁 OSSpinLockLock(lock); //解锁 OSSpinLockUnlock(lock);复制代码
9
输出结果为
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
0
由输出可知,能保证线程安全,数据没有错乱。
YZNSRecursiveLock
是对mutex
递归锁的封装
如果想证明NSRecursiveLock
是对mutex
普通锁的封装有两种方式
汇编分析
汇编分析来说,可以打断点跟进去,最终会发现调用了
mutex
,因为,lock是调用的msgSend,汇编代码比较复杂,读者有兴趣可自行验证。
GNUstep源码的NSLock.m中的NSRecursiveLock有如下代码
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
1
NSCondition
条件锁
NSCondition是对mutex和cond的封装
使用
生产者消费者
同上面的YZMutexCondLock
一样使用生产者消费者模式
api
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
2
代码
初始化
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
3
eg:
YZNSCondition
继承YZBaseLock
,otherTest
里面进行测试
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
4
调用的时候
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
5
输出结果为:
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
6
由结果可知,打印完__remove - 等待
之后,等待了一秒钟,添加元素之后,放开锁,才去删除元素。
NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
API
主要有如下几个API,顾名思义,一看名字就懂了。
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
7
使用
初始化
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
8
eg:
YZNSConditionLock
继承YZBaseLock
,otherTest
里面进行测试
#import "YZOSSpinLock.h"#import@interface YZOSSpinLock() @property (assign, nonatomic) OSSpinLock moneyLock; @end @implementation YZOSSpinLock - (instancetype)init {if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; }return self; } - (void)__drawMoney { OSSpinLockLock(&_moneyLock); [super __drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)__saveMoney { OSSpinLockLock(&_moneyLock); [super __saveMoney]; OSSpinLockUnlock(&_moneyLock); } @end复制代码
9
调用的时候
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
0
输出结果为:
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
1
由结果可知,NSConditionLock完全能够通过条件值进行加锁解锁。
dispatch_semaphore
信号量
semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
关于信号量详细可以参考GCD信号量-dispatch_semaphore_t
以及对信号量实际应用,结合RunLoop做成卡顿监控的iOS使用RunLoop监控线上卡顿
信号量原理
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
2
dispatch_semaphore_create(long value)
和GCD的group等用法一致,这个函数是创建一个dispatch_semaphore_
类型的信号量,并且创建的时候需要指定信号量的大小。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
等待信号量。如果信号量值为0,那么该函数就会一直等待,也就是不返回(相当于阻塞当前线程),直到该函数等待的信号量的值大于等于1,该函数会对信号量的值进行减1操作,然后返回。
dispatch_semaphore_signal(dispatch_semaphore_t deem)
发送信号量。该函数会对信号量的值进行加1操作。
通常等待信号量和发送信号量的函数是成对出现的。并发执行任务时候,在当前任务执行之前,用
dispatch_semaphore_wait
函数进行等待(阻塞),直到上一个任务执行完毕后且通过dispatch_semaphore_signal
函数发送信号量(使信号量的值加1),dispatch_semaphore_wait
函数收到信号量之后判断信号量的值大于等于1,会再对信号量的值减1,然后当前任务可以执行,执行完毕当前任务后,再通过dispatch_semaphore_signal
函数发送信号量(使信号量的值加1),通知执行下一个任务......如此一来,通过信号量,就达到了并发队列中的任务同步执行的要求。
使用
先看加锁,解锁的使用,初始化先设置1,然后每次取钱,存钱之前,都调用dispatch_semaphore_wait
,取钱,存钱之后调用dispatch_semaphore_signal
eg:
YZSemaphore
继承YZBaseLock
,otherTest
里面进行测试
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
3
外部调用的时候,
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
4
输出
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
5
有结果可知,能保证多线程数据的安全读写。
使用二
信号量还可以控制线程数量,例如初始化的时候,设置最多3条线程
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
6
调用otherTest
的输出结果为
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
7
由结果可知,每次最多三条线程执行。
synchronized
@synchronized是对mutex递归锁的封装
源码查看:objc4中的objc-sync.mm文件
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
详细了解可以参考 关于 @synchronized,这儿比你想知道的还要多
使用
@synchronized 使用起来很简单
还使用前面的存钱取票的例子,类YZSynchronized继承自YZBaseLock,代码如下
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
8
调用之后
iOS-LockDemo[22496:265962] 取20元,还剩80元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩60元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩40元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩20元 -{number = 3, name = (null)} iOS-LockDemo[22496:265962] 取20元,还剩0元 -{number = 3, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩10元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩20元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩30元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩40元 -{number = 4, name = (null)} iOS-LockDemo[22496:265961] 存10元,还剩50元 -{number = 4, name = (null)}复制代码
9
可知,多线程的数据没有发生错乱
源码分析
从runtime源码中的objc-sync.mm中可知
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
0
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
1
以及
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
2
可知@synchronized是对mutex递归锁的封装。因为是递归锁,可以递归加锁,读者有兴趣自行验证。
pthread_rwlock
读写锁
读写锁是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁。多读者锁,“push lock”) 用于解决读写问题。读操作可并发重入,写操作是互斥的。
需要导入头文件#import
使用
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
3
eg:
YZRwlock
继承YZBaseLock
,otherTest
里面进行测试,每次读,或者写的之前进行加锁,并sleep 1秒钟,之后解锁,如下所示
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
4
调用的时候
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
5
输出结果为:
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
6
由结果可知,打印完write
之后,方法每次都是一个一个执行的,而read
是可以同时执行的,也就是说达到了多读单写的功能。被称为读写锁。
dispatch_barrier_async
异步栅栏
这个函数传入的并发队列必须是自己通过
dispatch_queue_cretate
创建的如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于
dispatch_async
函数的效果
使用
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
7
eg:
YZBarrier
继承YZBaseLock
,otherTest
里面进行测试
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
8
调用的时候
// 初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁(如果不需要等待,就直接加锁,返回true。如果需要等待,就不加锁,返回false) BOOL res = os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);复制代码
9
输出结果为:
#import "YZUnfairLock.h"#import@interface YZUnfairLock() @property (nonatomic ,assign) os_unfair_lock moneyLock; @end @implementation YZUnfairLock - (instancetype)init { if (self = [super init]) { self.moneyLock = OS_UNFAIR_LOCK_INIT; }return self; } - (void)__saveMoney { os_unfair_lock_lock(&_moneyLock); [super __saveMoney]; os_unfair_lock_unlock(&_moneyLock); } - (void)__drawMoney { os_unfair_lock_lock(&_moneyLock); [super __drawMoney]; os_unfair_lock_unlock(&_moneyLock); } @end复制代码
0
由结果可知,打印完write
之后,方法每次都是一个一个执行的,而read
是可以同时执行的,但是遇到写的操作,就会把其他读或者写都会暂停,也就是说起到了栅栏的作用。
dispatch_group_t
调度组
前面说了这么多关于锁的使用,其实调度组也能达到类似栅栏的效果。
api
#import "YZUnfairLock.h"#import@interface YZUnfairLock() @property (nonatomic ,assign) os_unfair_lock moneyLock; @end @implementation YZUnfairLock - (instancetype)init { if (self = [super init]) { self.moneyLock = OS_UNFAIR_LOCK_INIT; }return self; } - (void)__saveMoney { os_unfair_lock_lock(&_moneyLock); [super __saveMoney]; os_unfair_lock_unlock(&_moneyLock); } - (void)__drawMoney { os_unfair_lock_lock(&_moneyLock); [super __drawMoney]; os_unfair_lock_unlock(&_moneyLock); } @end复制代码
1
eg:
YZDispatchGroup
继承YZBaseLock
,otherTest
里面进行测试,假设的场景是,需要在子线程下载两个图片,sleep()模拟耗时操作,都下载完成之后,回到主线程刷新UI.
#import "YZUnfairLock.h"#import@interface YZUnfairLock() @property (nonatomic ,assign) os_unfair_lock moneyLock; @end @implementation YZUnfairLock - (instancetype)init { if (self = [super init]) { self.moneyLock = OS_UNFAIR_LOCK_INIT; }return self; } - (void)__saveMoney { os_unfair_lock_lock(&_moneyLock); [super __saveMoney]; os_unfair_lock_unlock(&_moneyLock); } - (void)__drawMoney { os_unfair_lock_lock(&_moneyLock); [super __drawMoney]; os_unfair_lock_unlock(&_moneyLock); } @end复制代码
2
调用的时候
#import "YZUnfairLock.h"#import@interface YZUnfairLock() @property (nonatomic ,assign) os_unfair_lock moneyLock; @end @implementation YZUnfairLock - (instancetype)init { if (self = [super init]) { self.moneyLock = OS_UNFAIR_LOCK_INIT; }return self; } - (void)__saveMoney { os_unfair_lock_lock(&_moneyLock); [super __saveMoney]; os_unfair_lock_unlock(&_moneyLock); } - (void)__drawMoney { os_unfair_lock_lock(&_moneyLock); [super __drawMoney]; os_unfair_lock_unlock(&_moneyLock); } @end复制代码
3
输出结果为:
#import "YZUnfairLock.h"#import@interface YZUnfairLock() @property (nonatomic ,assign) os_unfair_lock moneyLock; @end @implementation YZUnfairLock - (instancetype)init { if (self = [super init]) { self.moneyLock = OS_UNFAIR_LOCK_INIT; }return self; } - (void)__saveMoney { os_unfair_lock_lock(&_moneyLock); [super __saveMoney]; os_unfair_lock_unlock(&_moneyLock); } - (void)__drawMoney { os_unfair_lock_lock(&_moneyLock); [super __drawMoney]; os_unfair_lock_unlock(&_moneyLock); } @end复制代码
4
由结果可知,子线程耗时操作,现在图片时候,主线程刷新UI不执行的,等两个图片都下载完成,才回到主线程刷新UI.
dispatch_group
有两个需要注意的地方
dispatch_group_enter必须在dispatch_group_leave之前出现
dispatch_group_enter和dispatch_group_leave必须成对出现
自旋锁,互斥锁的选择
前面这么多锁,那么到底平时开发中怎么选择呢?其实主要参考如下标准来选择。
什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器
什么情况使用互斥锁比较划算?
预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈