在iOS开发中,经常会用到多线程开发的情况,这时候如果多个线程同时访问同一块资源时,就很容易引发数据错乱和数据安全的问题,这个时候我们就需要有一个方法来保证每次只有一个线程访问这一块资源,这个方法就是锁。
iOS中一共有8中常用的锁,我们用YY大神的一张图来看一下这些锁的性能。
OSSpinLock 自旋锁
OSSpinLock 自旋锁在图表中可以看到,性能是最高的,大概在150us,但是YY大神在不再安全的 OSSpinLock中说道OSSpinLock自旋锁已经不再安全了。
而且也可以看到OSSpinLock已经在iOS 10.0之后废弃了。
好了,接下来看一下OSSpinLock中的方法。
- OS_SPINLOCK_INIT: 默认值为 0,在 locked 状态时就会大于 0,unlocked状态下为 0
- OSSpinLockLock(&oslock):上锁,参数为 OSSpinLock 地址
- OSSpinLockUnlock(&oslock):解锁,参数为 OSSpinLock 地址
- OSSpinLockTry(&oslock):尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
接下来我们测试一下,比如下面一段代码中,我们开启两个异步线程。
|
|
可以看到打印出来的数据都是错乱的:
|
|
这时候,我们添加一下上OSSpinLock自旋锁:
首先引入OSSpinLock自旋锁的库:
|
|
|
|
可以看到打印的数据:
|
|
dispatch_semaphore 信号量
信号量是仅次于自旋锁的方法,使用的是GCD提供的API:
- dispatch_semaphore_create 创建一个Semphore并初始化信号的总量。
- dispatch_semaphore_signal 发送一个信号,让信号总量加1。
- dispatch_semaphore_wait 可以使总信号量减1,让信号总量为0时就会一直等待,否则就可以正常执行。
这次我们就不上测试的代码了,直接放上增加信号量的代码:
|
|
打印结果:
|
|
pthread_mutex 互斥锁
YY大神在之前的文章中已经说过OSSpinLock已经不再是线程安全的并把自己开源项目中的OSSpinLock都换成了pthread_mutex。
接下来我们看一下互斥锁提供的方法:
- pthread_mutex_init(&pLock, NULL);:参数为 pthread_mutex 地址,第二个参数默认PTHREAD_MUTEX_NORMAL
- pthread_mutex_lock(&pLock):上锁,参数为 pthread_mutex 地址
- pthread_mutex_unlock(&pLock):解锁,参数为 pthread_mutex 地址
- pthread_mutex_trylock(&pLock):尝试加锁,可以加锁则立即加锁并返回 0 ,否者返回一个错误提示码。
接下来看代码:
|
|
然后是打印结果:
|
|
pthread_mutex(recursive) 递归锁
递归锁最大的特点就是允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。而之前介绍的几种锁可以看到,加锁后只能有一个线程访问该对象,后面的线程需要排队,并且lock和unlock都是对应出现的。
下面是代码:
|
|
打印结果:
|
|
NSLock
NSLock看名字就会熟悉一些了,我们来看一下他的API:
- lock:上锁
- unlock:解锁
- trylock:能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO。
- lockBeforeDate:这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO。
接下来上代码:
|
|
打印结果:
|
|
NSCondition
这个方法和信号量有点相似:
- wait:进入等待状态
- waitUntilDate::让一个线程等待一定的时间
- signal:唤醒一个等待的线程
- broadcast:唤醒所有等待的线程
线程延迟执行
上代码:
|
|
可以看到打印代码中number=11
的代码在第一次输出的3s之后。
|
|
唤醒等待线程
上代码:
|
|
打印结果:
|
|
唤醒所有等待线程
|
|
打印结果:
|
|
@synchronized 条件锁
@synchronized 应该是一种比较简单的锁。
直接上代码:
|
|
打印结果:
|
|
NSConditionLock 条件锁
这个锁和NSLock相差不大,多了一个condition参数,可以理解为一个条件标示。也可以实现任务之前的互相依赖。
放代码:
|
|
打印结果:
|
|
由上可知,我们在初始化 NSConditionLock 对象时,给了他的标示为 0;执行 tryLockWhenCondition:时,我们传入的条件标示也是 0,所 以线程1 加锁成功;执行 unlockWithCondition:时,这时候会把condition由 0 修改为 1;因为condition 修改为了 1, 会先走到 线程3,然后 线程3 又将 condition 修改为 3,最后走了线程2 的流程。
最后
以上就是全部的内容,还是放一个demo在这里吧。