经过以前篇章的学习,咱们对整个GCD从使用到原理,都有了必定的理解。这篇主要讲解一下iOS开发中的锁是什么状况html
系列文章传送门:ios
☞ iOS底层学习 - 多线程之GCD队列原理篇swift
锁 -- 是保证线程安全常见的同步工具。锁是一种非强制的机制,每个线程在访问数据或者资源前,要先获取(Acquire
) 锁,并在访问结束以后释放(Release
)锁。若是锁已经被占用,其它试图获取锁的线程会等待,直到锁从新可用。安全
前面说到了,锁是用来保护线程安全的工具。markdown
能够试想一下,多线程编程时,没有锁的状况 -- 也就是线程不安全。多线程
当多个线程同时对一块内存发生读和写的操做,可能出现意料以外的结果:
程序执行的顺序会被打乱,可能形成提早释放一个变量,计算结果错误等状况。
因此咱们须要将线程不安全的代码 “锁” 起来。保证一段代码或者多段代码操做的原子性,保证多个线程对同一个数据的访问 同步 (Synchronization
)。
锁的分类方式,能够根据锁的状态,锁的特性等进行不一样的分类,不少锁之间其实并非并列的关系,而是一种锁下的不一样实现。能够看这篇文章JAVA中锁的分类
互斥锁:是⼀种⽤于多线程编程中,防⽌两条线程同时对同⼀公共资源(⽐ 如全局变量)进⾏读写的机制。当获取锁操做失败时,线程会进入睡眠,等待锁释放时被唤醒。 互斥锁又分为递归锁和非递归锁。
⾃旋锁:线程反复检查锁变量是否可⽤。因为线程在这⼀过程当中保持执⾏, 所以是⼀种忙等待。⼀旦获取了⾃旋锁,线程会⼀直保持该锁,直⾄显式释 放⾃旋锁。 ⾃旋锁避免了进程上下⽂的调度开销,所以对于线程只会阻塞很 短期的场合是有效的。
其实就是线程的区别,互斥锁在线程获取锁但没有获取到时,线程会进入休眠状态,等锁被释放时,线程会被唤醒,而自旋锁的线程则会一直处于等待状态,忙等待,不会进入休眠。
相信你们都拜读过这片文章->再也不安全的 OSSpinLock。总结来讲,自旋锁之因此不安全,是由于因为自旋锁获取锁时,线程会一直处于忙等待状态,形成了任务的优先级反转。
而 OSSpinLock
忙等的机制,就可能形成高优先级一直 running
,占用 CPU
时间片。而低优先级任务没法抢占时间片,变成迟迟完不成,不释放锁的状况。
在面试中,咱们常常遇到关于atomic
相关的问题,总结来讲主要是两个方面,一个是atomic
的底层原理是怎样的,另外一个是使用atomic
是否就能保证线程安全。
关于底层原理,咱们仍是来看源码进行探索。经过源码,咱们能够发现,在方法的set
和get
方法中,会有是不是atomic
的判断,若是不是的话,则直接进行赋值,若是是的话,会加一个spinlock_t
的锁,这个锁保证了对属性读写的安全。
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
// ...
if (!atomic) {
// 不是 atomic 修饰
oldValue = *slot;
*slot = newValue;
} else {
// 若是是 atomic 修饰,加一把同步锁,保证 setter 的安全
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
}
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
// ...
// 非原子属性,直接返回值
if (!atomic) return *slot;
// 原子属性,加同步锁,保证 getter 的安全
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
}
复制代码
既然atomic
是保证set
和get
方法安全的,那是否是就说明其线程安全呢?其实并非的,这只能保证该属性在单一线程上是安全的,若是是有不少的线程对该属性进行同时的操做,那么就不能保证其数据安全了.好比下面的代码,经过结果咱们能够看到,并无起到加锁的效果。
//Thread A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 100; i ++) {
self.num = self.num + 1;
NSLog(@"Thread A:%ld\n",self.num);
}
});
//Thread B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 100; i ++) {
NSLog(@"Thread B:%ld\n",self.num);
}
});
-------------------------------------------------------------
Thread A:1
Thread B:1
Thread B:2
Thread A:2
复制代码
读写锁实际是⼀种特殊的⾃旋锁,它把对共享资源的访问者划分红读者和写者,读者只对共享资源进⾏读访问,写者则须要对共享资源进⾏写操做。这种锁相对于⾃旋锁⽽⾔,能提⾼并发性,由于在多处理器系统中,它容许同时有多个读者来访问共享资源,最⼤可能的读者数为实际的逻辑CPU数。
写者是排他性的,⼀个读写锁同时只能有⼀个写者或多个读者(与CPU数相关),但不能同时既有读者⼜有写者。在读写锁保持期间也是抢占失效的。
若是读写锁当前没有读者,也没有写者,那么写者能够⽴刻得到读写锁,不然它必须⾃旋在那⾥,直到没有任何写者或读者。若是读写锁没有写者,那么读者能够⽴即得到该读写锁,不然读者必须⾃旋在那⾥,直到写者释放该读写锁。
具体用法以下,不过在平常开发中较少使用
// 须要导入头文件
#include <pthread.h>
pthread_rwlock_t lock;
// 初始化锁
pthread_rwlock_init(&lock, NULL);
// 读-加锁
pthread_rwlock_rdlock(&lock);
// 读-尝试加锁
pthread_rwlock_tryrdlock(&lock);
// 写-加锁
pthread_rwlock_wrlock(&lock);
// 写-尝试加锁
pthread_rwlock_trywrlock(&lock);
// 解锁
pthread_rwlock_unlock(&lock);
// 销毁
pthread_rwlock_destroy(&lock);
复制代码
咱们可使用并发队列+dispatch_barrier_async来实现一个相似的读写锁
########### .h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WY_RWLock : NSObject
// 读数据
- (id)wy_objectForKey:(NSString *)key;
// 写数据
- (void)wy_setObject:(id)obj forKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END
########### .m文件
#import "WY_RWLock.h"
@interface WY_RWLock ()
// 定义一个并发队列:
@property (nonatomic, strong) dispatch_queue_t concurrent_queue;
// 多个线程须要数据访问
@property (nonatomic, strong) NSMutableDictionary *dataCenterDic;
@end
@implementation WY_RWLock
- (id)init{
self = [super init];
if (self){
// 建立一个并发队列:
self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 建立数据字典:
self.dataCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
#pragma mark - 读数据
- (id)wy_objectForKey:(NSString *)key{
__block id obj;
// 同步读取指定数据:
dispatch_sync(self.concurrent_queue, ^{
obj = [self.dataCenterDic objectForKey:key];
});
return obj;
}
#pragma mark - 写数据
- (void)wy_setObject:(id)obj forKey:(NSString *)key{
// 异步栅栏调用设置数据:
dispatch_barrier_async(self.concurrent_queue, ^{
[self.dataCenterDic setObject:obj forKey:key];
});
}
@end
复制代码
由于互斥锁出现优先级反转后,高优先级的任务不会忙等。由于处于等待状态的高优先级任务,没有占用时间片,因此低优先级任务通常都能进行下去,从而释放掉锁。
@synchronized
的使用很是简单,代码以下,传入一个想要加锁的对象,在其中执行加锁的相关逻辑便可。
@synchronized (obj) {}
复制代码
那么其底层逻辑是如何实现的呢,咱们能够看一下@synchronized
的源码,经过打断点,查看其汇编源码,发现@synchronized
就是实现了objc_sync_enter
和 objc_sync_exit
两个方法,也就是说是经过这两个方法来实现加锁和解锁操做的。经过符号断点,咱们能够知道其代码在objc
源码中。
首先注意enter
和exit
中都首先对obj
是否为nil
作了判断,若是obj为空时,则不会进行加锁和解锁的相关操做。因此在使用时必定要注意传入的值会不会被析构,形成传入值为空的状况,从而加锁失败。
好比在线程异步同时操做同一个对象时,由于递归锁会不停的alloc/release
,这时候某一个对象会多是nil
,从而致使加锁失败
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
✅// 若是obj为空,则不进行加锁操做
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 {
✅// 若是obj为空,则不进行解锁操做
// @synchronized(nil) does nothing
}
return result;
}
复制代码
在具体的实现逻辑中,咱们能够看到经过id2data
方法,对obj
进行了捕获和释放的操做,并生成了一个SyncData
类型的对象。咱们发现SyncData
是一个结构体,并且有一个SyncData
类型的nextData
变量,指向下个数据,因此咱们能够知道SyncData
是一个链表结构中的一个元素。因此这是一个递归锁。
nextData
指的是链表中下一个元素object
指的是传入须要加解锁的对象threadCount
就表示当前的线程数量mutex
即对象所关联的锁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;
复制代码
了解了SyncData
结构后,咱们继续来查看源码,因为源码比较长,因此咱们分模块俩讲解。
SyncData
咱们能够看到会会经过LOCK_FOR_OBJ
和LIST_FOR_OBJ
取出object
所对应的lockp
和listp
。
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
...
}
复制代码
既然咱们在任何地方均可以直接经过调用方法来使用,那么说明底层必然维护着一套内部的存储。经过代码咱们也能够看出,系统在底层维护了一个哈希表,里面存储了SyncList
结构的数据,而SyncList
是一个结构体,包含一个SyncData
的头结点和一个spinlock_t
锁对象
-----------------------------------------------------------------------------------------------------------------------
struct SyncList {
SyncData *data;
spinlock_t lock;
constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};
-----------------------------------------------------------------------------------------------------------------------
// Use multiple parallel lists to decrease contention among unrelated objects.
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;
复制代码
此步操做会经过tls
封装的相关pthead
操做线程的相关增删改查方法,获取到单个线程中缓存的SyncData
数据,并进行快速查询和缓存
static SyncData* id2data(id object, enum usage why)
{
...
#if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
✅// 检查每线程单项快速缓存中是否有匹配的对象
bool fastCacheOccupied = NO;
✅// 经过tls相关封装的pthead方法获取是否有再底层存储的SyncData
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
✅// 若是获取到的数据和传入数据相同
if (data->object == object) {
// Found a match in fast cache.
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: {
// 若是是 entry,则对 lockCount 加 1,并经过 tls 保存
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE:
// 若是是 exit,则对 lockCount 减 1,并经过 tls 保存
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
// 若是 lockCount 为 0,则从高速缓存中删除
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
...
}
复制代码
这步操做是检查全部线程中的缓存
static SyncData* id2data(id object, enum usage why)
{
...
// Check per-thread cache of already-owned locks for matching object
// 检查已拥有锁的每一个线程高速缓存中是否有匹配的对象
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[i];
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:
item->lockCount++;
break;
case RELEASE:
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
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
...
}
复制代码
若是上述两步中,单个线程和已经锁住的线程中的缓存数据都没有找到的话,那么就会来到此步,回来系统保存的哈希表中SyncList
结果中,进行链式查找。
static SyncData* id2data(id object, enum usage why)
{
...
{
SyncData* p;
SyncData* firstUnused = NULL;
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
...
}
复制代码
static SyncData* id2data(id object, enum usage why)
{
...
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:
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.
✅// 只有建立的 SyncData 才能进入这里。
✅// 全部的释放、检查和递归获取都是由上面的线程缓存处理
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");
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) {
// 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
✅// 存入线程缓存
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
复制代码
至此一个@synchronized的相关操做已经执行完成。总结来讲就是底层保存了一个哈希表,其中存储了
SyncData
结构的一个链表,经过线程缓存等操做,来进行增删改查,历来实现加解锁。可是操做结构复杂,步骤多,致使性能较滴,并且须要注意传入的obj不能为空,不然没法进行锁操做。
相关信号量的底层原理,再上一章节已经讲过,能够直接查看☞iOS底层学习 - 多线程之GCD底层原理篇
NSLock
的使用也很是的简单,只须要再须要进行加锁逻辑的先后,加上[_lock lock]
和[_lock unlock]
两行代码,就能够实现加锁的逻辑。
在寻找源码中,咱们发现NSLock
源码在CoreFundation
框架中,没法进行查看,因此咱们看Swift
版本的CoreFundation
实现,来类比NSLock
实现,应该也是差很少的。经过源码咱们能够发现
NSLock
就是对pthread_mutex
互斥锁的一种上层封装。open class NSLock: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
#if os(macOS) || os(iOS) || os(Windows)
private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
#endif
public override init() {
pthread_mutex_init(mutex, nil)
#if os(macOS) || os(iOS)
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
#endif
}
open func lock() {
pthread_mutex_lock(mutex)
}
open func unlock() {
pthread_mutex_unlock(mutex)
#if os(macOS) || os(iOS)
// Wakeup any threads waiting in lock(before:)
pthread_mutex_lock(timeoutMutex)
pthread_cond_broadcast(timeoutCond)
pthread_mutex_unlock(timeoutMutex)
#endif
}
复制代码
既然NSLock
不是递归锁,那么他就存在着一个坑点:当咱们对同一个线程,加锁两次的话,就会形成一直阻塞,就好比下面的代码,多线程调用时,会形成lock
屡次,从而没法向下进行。这个时候可使用递归锁来解决。
NSLock *testlock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[testlock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
// 异步递归调用
testMethod(value - 1);
}
[testlock unlock];
};
testMethod(10);
});
复制代码
将上面例子中的NSLock
换成NSRecursiveLock
就是递归锁的使用了,和NSLock
是相似的,而且可以解决NSLock
在多线程中屡次加锁的问题。
首先咱们仍是来看一下源码实现,发现NSRecursiveLock
也是对pthread_mutex
的封装,可是初始化的时候添加了PTHREAD_MUTEX_RECURSIVE
递归相关的操做。
open class NSRecursiveLock: NSObject, NSLocking {
internal var mutex = _RecursiveMutexPointer.allocate(capacity: 1)
private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
withUnsafeMutablePointer(to: &attrib) { attrs in
pthread_mutexattr_init(attrs)
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
pthread_mutex_init(mutex, attrs)
}
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
public override init() {
super.init()
var attrib = pthread_mutexattr_t()
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
}
deinit {
pthread_mutex_destroy(mutex)
mutex.deinitialize(count: 1)
mutex.deallocate()
deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
}
open func lock() {
pthread_mutex_lock(mutex)
}
open func unlock() {
pthread_mutex_unlock(mutex)
// Wakeup any threads waiting in lock(before:)
pthread_mutex_lock(timeoutMutex)
pthread_cond_broadcast(timeoutCond)
pthread_mutex_unlock(timeoutMutex)
}
open func `try`() -> Bool {
return pthread_mutex_trylock(mutex) == 0
}
open func lock(before limit: Date) -> Bool {
if pthread_mutex_trylock(mutex) == 0 {
return true
}
return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
}
open var name: String?
}
复制代码
咱们都知道,使用递归的时候,最主要的是要有一个出口,不然很是容易造成死锁。好比刚才的代码,若是进行for循环建立多线程时。这时候就是形成死锁崩溃。
由于这个时候for循环形成多线程的屡次建立,开辟了多条线程,可是NSRecursiveLock
对象只有一个,线程之间同一个锁的对象状态是不能共享的,因此形成了线程1进行lock后,未执行到unlock时,线程2就进行了lock,因此形成了线程 1 等线程 2 解锁,线程 2 等线程 1 解锁的死锁情况。
那么这种状况下,使用哪一种方案比较好呢?
这个时候使用@synchronized
能够完美解决问题,由于@synchronized
锁的是同一个对象,下次线程来进行锁操做时,会先从缓存中进行查找,不会进行屡次锁,因此是安全的。
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
for (int i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[recursiveLock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[recursiveLock unlock];
};
testMethod(10);
});
}
复制代码
经常使用锁总结:当只是普通线程安全的时候,使用 NSLock就能够解决,而须要保证递归调用线程安全的时候,使用 NSRecursiveLock,而又须要循环,外界的线程也会形成影响的时候,为了解决死锁的问题,咱们可使用@synchronized来解决
复制代码
NSCondition
是一个条件锁。
在线程间的同步中,有这样一种状况: 线程 A 须要等条件 C 成立,才能继续往下执行.如今这个条件不成立,线程 A 就阻塞等待. 而线程 B 在执行过程当中,使条件 C 成立了,就唤醒线程 A 继续执行。这个时候,咱们可使用条件锁来完成相关逻辑。
条件锁的底层实现其实就是一个互斥锁和条件变量的封装,因为未开源,咱们仍是先看Swift源码。
NSCondition
是对mutex
和cond
的一种封装。cond
就是用于访问和操做特定类型数据的指针wait
操做在没有超时时,会阻塞线程,使其进入休眠状态,须要在lock
状态下使用signal
操做是唤醒一个正在休眠等待的线程,须要在lock
状态下使用broadcast
唤醒全部正在等待的线程,须要在lock
状态下使用open class NSCondition: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
// 用于访问和操做特定类型数据的指针
internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
public override init() {
pthread_mutex_init(mutex, nil)
pthread_cond_init(cond, nil)
}
open func lock() {
pthread_mutex_lock(mutex)
}
open func unlock() {
pthread_mutex_unlock(mutex)
}
open func wait() {
pthread_cond_wait(cond, mutex)
}
open func wait(until limit: Date) -> Bool {
// 超时
guard var timeout = timeSpecFrom(date: limit) else {
return false
}
// 没有超时
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
}
open func signal() {
pthread_cond_signal(cond)
}
open func broadcast() {
pthread_cond_broadcast(cond) // wait signal
}
}
复制代码
对于条件锁,咱们常常用来解决的就是生产者-消费者模式
的相关问题。好比数组中的元素,只有在大于0的状况下,才能够进行删除操做,这种状况下,能够考虑使用条件锁。
_condition = [[NSCondition alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer];
});
- (void)producer{
[_condition lock];
self.ticketCount = self.ticketCount + 1;
NSLog(@"生产一个 现有 count %zd",self.ticketCount);
[_condition signal];
[_condition unlock];
}
- (void)consumer{
// 线程安全
[_condition lock];
✅// 使用while由于NSCondition能够给每一个线程分别加锁,但加锁后不影响其余线程进入临界区。
✅// 因此 NSCondition使用 wait并加锁后,并不能真正保证线程的安全。
✅// 当一个signal操做发出时,若是有两个线程都在作消费者操做,那同时都会消耗掉资源,因而绕过了检查。
while (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
// 保证正常流程
[_condition wait];
}
//注意消费行为,要在等待条件判断以后
self.ticketCount -= 1;
NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
[_condition unlock];
}
复制代码
NSConditionLock
。咱们能够经过Swift源码查看可得
NSConditionLock
是NSCondition
加线程数的封装,继承NSLocking
协议,也有lock
和unlock
等方法dispatch_semaphore
的效果open class NSConditionLock : NSObject, NSLocking {
internal var _cond = NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?
public convenience override init() {
self.init(condition: 0)
}
public init(condition: Int) {
_value = condition
}
open func lock() {
let _ = lock(before: Date.distantFuture)
}
open func unlock() {
_cond.lock()
_thread = nil
_cond.broadcast()
_cond.unlock()
}
open var condition: Int {
return _value
}
open func lock(whenCondition condition: Int) {
let _ = lock(whenCondition: condition, before: Date.distantFuture)
}
open func `try`() -> Bool {
return lock(before: Date.distantPast)
}
open func tryLock(whenCondition condition: Int) -> Bool {
return lock(whenCondition: condition, before: Date.distantPast)
}
open func unlock(withCondition condition: Int) {
_cond.lock()
_thread = nil
_value = condition
_cond.broadcast()
_cond.unlock()
}
open func lock(before limit: Date) -> Bool {
_cond.lock()
while _thread != nil {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
// 使用 NSCondition 加锁
_cond.lock()
while _thread != nil || _value != condition {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
open var name: String?
}
复制代码
具体的用法能够参考下面的代码
// 初始化 NSConditionLock,并设置 condition 的值为 2
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// 须要等到 condition 为 1 的时候执行下面的代码
[conditionLock lockWhenCondition:1];
NSLog(@"线程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// 由于 condition 为 2,因此执行下面的代码
[conditionLock lockWhenCondition:2];
NSLog(@"线程 2");
// 解锁,并将 condition 设置为 1
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 由于没有条件限制,因此能够直接执行下面的代码
[conditionLock lock];
NSLog(@"线程 3");
[conditionLock unlock];
});
-----------------------------------------------------------------------------------------------------------------------
// 打印结果
线程 3
线程 2
线程 1
复制代码
因为OSSpinLock
自旋锁的bug,在iOS10以后OSSpinLock被废弃,内部封装了os_unfair_lock
,而os_unfair_lock
在加锁时会处于休眠状态,而不是自旋锁的忙等状态。
OSSpinLock
之因此不在安全,是由于自旋锁会在线程等待时处于忙等状态,会形成任务优先级翻转,却是没法执行,目前用os_unfair_lock
来替代,是一个互斥锁,互斥锁不会处于忙等,不占用时间片。atomic
底层实现原理就是对get
和set
方法进行加锁,可是不能保证多条线程调用或者不适用get
和set
的线程安全,且性能消耗巨大并发队列+dispatch_barrier_async
的方法,来实现一个相似的读写锁@synchronized
要注意传入的对象不能为nil
,不然没法加锁。底层逻辑是维护了一个全局的哈希表用来存储对象和锁,会按照缓存线程->全部线程->全局哈希表
的方式进行增删改查NSLock
是对pthread_mutex
的封装,可是没有递归逻辑。对同一个线程屡次lock
会形成阻塞。NSRecursiveLock
是在NSLock
的基础上添加了递归逻辑,当只有一个递归锁对象,多线程进行锁操做时,会形成死锁,可用@synchronized
解决NSCondition
和NSConditionLock
是条件锁,当知足某一个条件时,才能进行操做,适用于生产者消费者模式,和信号量dispatch_semaphore
相似