辽宁省营商环境建设监督局网站,肇庆企业网站关键词优化教程,做户外旅游网站,零基础学wordpress课件多线程在日常开发中能起到性能优化的作用#xff0c;但是一旦没用好就会造成线程不安全#xff0c;本文就来讲讲如何保证线程安全
锁
线程安全
当一个线程访问数据的时候#xff0c;其他的线程不能对其进行访问#xff0c;直到该线程访问完毕。简单来讲就是在同一时刻但是一旦没用好就会造成线程不安全本文就来讲讲如何保证线程安全
锁
线程安全
当一个线程访问数据的时候其他的线程不能对其进行访问直到该线程访问完毕。简单来讲就是在同一时刻对同一个数据操作的线程只有一个。而线程不安全则是在同一时刻可以有多个线程对该数据进行访问从而得不到预期的结果 即线程内操作了一个线程外的非线程安全变量这个时候一定要考虑线程安全和同步
锁的作用
锁作为一种非强制的机制被用来保证线程安全。每一个线程在访问数据或者资源前要先获取Acquire锁并在访问结束之后释放Release锁。如果锁已经被占用其它试图获取锁的线程会等待直到锁重新可用 注不要将过多的其他操作代码放到锁里面否则一个线程执行的时候另一个线程就一直在等待就无法发挥多线程的作用了
锁的分类
在iOS中锁的基本种类只有两种互斥锁、自旋锁其他的比如条件锁、递归锁、信号量都是上层的封装和实现
互斥锁
互斥锁(Mutual exclusion缩写Mutex)防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。当获取锁操作失败时线程会进入睡眠等待锁释放时被唤醒 互斥锁又分为
递归锁可重入锁同一个线程在锁释放前可再次获取锁即可以递归调用非递归锁不可重入必须等锁释放后才能再次获取锁
自旋锁
自旋锁线程反复检查锁变量是否可⽤。由于线程在这⼀过程中保持执⾏ 因此是⼀种忙等待。⼀旦获取了⾃旋锁线程会⼀直保持该锁直⾄显式释 放⾃旋锁
⾃旋锁避免了进程上下⽂的调度开销因此对于线程只会阻塞很短时间的场合是有效的
互斥锁和自旋锁的区别
互斥锁在线程获取锁但没有获取到时线程会进入休眠状态等锁被释放时线程会被唤醒自旋锁的线程则会一直处于等待状态忙等待不会进入休眠——因此效率高
自旋锁
OSSpinLock
自从OSSpinLock出现了安全问题之后就废弃了。自旋锁之所以不安全是因为自旋锁由于获取锁时线程会一直处于忙等待状态造成了任务的优先级反转 而OSSpinLock忙等的机制就可能造成高优先级一直running等待占用CPU时间片而低优先级任务无法抢占时间片变成迟迟完不成不释放锁的情况
atomic
atomic原理
在iOS探索 KVC原理及自定义中有提到自动生成的setter方法会根据修饰符不同调用不同方法最后统一调用reallySetProperty方法其中就有一段关于atomic修饰词的代码
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{if (offset 0) {object_setClass(self, newValue);return;}id oldValue;id *slot (id*) ((char*)self offset);if (copy) {newValue [newValue copyWithZone:nil];} else if (mutableCopy) {newValue [newValue mutableCopyWithZone:nil];} else {if (*slot newValue) return;newValue objc_retain(newValue);}if (!atomic) {oldValue *slot;*slot newValue;} else {spinlock_t slotlock PropertyLocks[slot];slotlock.lock();oldValue *slot;*slot newValue; slotlock.unlock();}objc_release(oldValue);
}比对一下atomic的逻辑分支
原子性修饰的属性进行了spinlock加锁处理 非原子性的属性除了没加锁其他逻辑与atomic一般无二
等等前面不是刚说OSSpinLock因为安全问题被废弃了吗但是苹果源码怎么还在使用呢其实点进去就会发现用os_unfair_lock替代了OSSpinLockiOS10之后替换
using spinlock_t mutex_ttLOCKDEBUG;class mutex_tt : nocopy_t {os_unfair_lock mLock;...
}同时为了哈希不冲突还使用加盐操作进行加锁
getter方法亦是如此atomic修饰的属性进行加锁处理
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {if (offset 0) {return object_getClass(self);}// Retain release worldid *slot (id*) ((char*)self offset);if (!atomic) return *slot;// Atomic retain release worldspinlock_t slotlock PropertyLocks[slot];slotlock.lock();id value objc_retain(*slot);slotlock.unlock();// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.return objc_autoreleaseReturnValue(value);
}atomic修饰的属性绝对安全吗
atomic只能保证setter、getter方法的线程安全并不能保证数据安全
//#import ViewController.h
#import os/lock.h
interface ViewController ()
property (atomic, assign) NSInteger index;
endimplementation ViewController- (void)viewDidLoad {[super viewDidLoad];self.index 0;dispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i 0; i 10000; i) {self.index self.index 1;NSLog(%d--%ld, i , (long)self.index);}});dispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i 0; i 10000; i) {self.index self.index 1;NSLog(%d--%ld, i, (long)self.index);}});//[self ticketTest];// Do any additional setup after loading the view.
}如上图所示被atomic修饰的index变量分别在两次并发异步for循环10000次后输出的结果并不等于20000。由此可以得出结论
atomic保证变量在取值和赋值时的线程安全但不能保证self.index1也是安全的如果改成self.indexi是能保证setter方法的线程安全的 读写锁
读写锁实际是一种特殊的自旋锁它把对共享资源的访问者划分成读者和写者读者只对共享资源进行读访问写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言能提高并发性因为在多处理器系统中它允许同时有多个读者来访问共享资源最大可能的读者数为实际的CPU数
写者是排他性的⼀个读写锁同时只能有⼀个写者或多个读者与CPU数相关但不能同时既有读者⼜有写者。在读写锁保持期间也是抢占失效的如果读写锁当前没有读者也没有写者那么写者可以⽴刻获得读写锁否则它必须⾃旋在那⾥直到没有任何写者或读者。如果读写锁没有写者那么读者可以⽴即获得该读写锁否则读者必须⾃旋在那⾥直到写者释放该读写锁
// 导入头文件
#import 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);平时很少会直接使用读写锁pthread_rwlock_t更多的是采用其他方式例如使用栅栏函数完成读写锁的需求
互斥锁
pthread_mutex
pthread_mutex就是互斥锁本身——当锁被占用而其他线程申请锁时不是使用忙等而是阻塞线程并睡眠
使用如下
// 导入头文件
#import pthread.h
// 全局声明互斥锁
pthread_mutex_t _lock;
// 初始化互斥锁
pthread_mutex_init(_lock, NULL);
// 加锁
pthread_mutex_lock(_lock);
// 这里做需要线程安全操作
// ...
// 解锁
pthread_mutex_unlock(_lock);
// 释放锁
pthread_mutex_destroy(_lock);YYKit的YYMemoryCach有使用到pthread_mutex
synchronized
synchronized可能是日常开发中用的比较多的一种互斥锁因为它的使用比较简单但并不是在任意场景下都能使用synchronized且它的性能较低
synchronized (obj) {}接下来就通过源码探索来看一下synchronized在使用中的注意事项
通过汇编能发现synchronized就是实现了objc_sync_enter和 objc_sync_exit两个方法通过符号断点能知道这两个方法都是在objc源码中的通过clang也能得到一些信息
int main(int argc, char * argv[]) {NSString * appDelegateClassName;/* autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; appDelegateClassName NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass(AppDelegate), sel_registerName(class)));{id _rethrow 0;id _sync_obj (id)appDelegateClassName;objc_sync_enter(_sync_obj);try {struct _SYNC_EXIT {_SYNC_EXIT(id arg) : sync_exit(arg) {}~_SYNC_EXIT() {objc_sync_exit(sync_exit);}id sync_exit;}_sync_exit(_sync_obj);}catch (id e) {_rethrow e;}{struct _FIN { _FIN(id reth) : rethrow(reth) {}~_FIN() { if (rethrow) objc_exception_throw(rethrow); }id rethrow;}_fin_force_rethow(_rethrow);}}}return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}源码分析
在objc源码中找到objc_sync_enter和objc_sync_exit
// Begin synchronizing on obj.
// Allocates recursive mutex associated with obj if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
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 nothingif (DebugNilSync) {_objc_inform(NIL SYNC DEBUG: synchronized(nil); set a breakpoint on objc_sync_nil to debug);}objc_sync_nil();}return result;
}// End synchronizing on obj.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{int result OBJC_SYNC_SUCCESS;if (obj) {SyncData* data id2data(obj, RELEASE); if (!data) {result OBJC_SYNC_NOT_OWNING_THREAD_ERROR;} else {bool okay data-mutex.tryUnlock();if (!okay) {result OBJC_SYNC_NOT_OWNING_THREAD_ERROR;}}} else {// synchronized(nil) does nothing}return result;
}首先从它的注释中recursive mutex可以得出synchronized是递归锁如果锁的对象obj不存在时分别会走objc_sync_nil()和不做任何操作源码分析可以先解决简单的逻辑分支
BREAKPOINT_FUNCTION(void objc_sync_nil(void)
);
这也是synchronized作为递归锁但能防止死锁的原因所在在不断递归的过程中如果对象不存在了就会停止递归从而防止死锁
正常情况下obj存在会通过id2data方法生成一个SyncData对象
nextData指的是链表中下一个SyncDataobject指的是当前加锁的对象threadCount表示使用该对象进行加锁的线程数mutex即对象所关联的锁
typedef struct alignas(CacheLineSize) SyncData {struct SyncData* nextData;DisguisedPtrobjc_object object;int32_t threadCount; // number of THREADS using this blockrecursive_mutex_t mutex;
} SyncData;准备SyncData
static SyncData* id2data(id object, enum usage why)
{spinlock_t *lockp LOCK_FOR_OBJ(object);SyncData **listp LIST_FOR_OBJ(object);SyncData* result NULL;...
}id2data先将返回对象SyncData类型的result准备好后续进行数据填充
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].datastatic StripedMapSyncList sDataLists;struct SyncList {SyncData *data;spinlock_t lock;constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};其中通过两个宏定义去取得SyncList中的data和lock——static StripedMapSyncList sDataLists 可以理解成 NSArrayid list 既然synchronized能在任意地方VC、View、Model等使用那么底层必然维护着一张全局的表类似于weak表。而从SyncList和SyncData的结构可以证实系统确实在底层维护着一张哈希表里面存储着SyncList结构的数据。SyncList和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;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: {lockCount;tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);break;}case RELEASE:lockCount--;tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);if (lockCount 0) {// remove from fast cachetls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);// atomic because may collide with concurrent ACQUIREOSAtomicDecrement32Barrier(result-threadCount);}break;case CHECK:// do nothingbreak;}return result;}}
#endif...
}这里有个重要的知识点——TLSTLS全称为Thread Local Storage在iOS中每个线程都拥有自己的TLS负责保存本线程的一些变量 且TLS无需锁保护 快速缓存的含义为定义两个变量SYNC_DATA_DIRECT_KEY/SYNC_COUNT_DIRECT_KEY与tsl_get_direct/tls_set_direct配合可以从线程局部缓存中快速取得SyncCacheItem.data和SyncCacheItem.lockCount 如果在缓存中找到当前对象就拿出当前被锁的次数lockCount再根据传入参数类型(获取、释放、查看)对lockCount分别进行操作
获取资源ACQUIRElockCount并根据key值存入被锁次数释放资源RELEASElockCount并根据key值存入被锁次数。如果次数变为0此时锁也不复存在需要从快速缓存移除并清空线程数threadCount查看资源check不操作 lockCount表示被锁的次数意味着能多次进入从侧面表现出了递归性 获取该线程下的SyncCache
这个逻辑分支是找不到确切的线程标记只能进行所有的缓存遍历
static SyncData* id2data(id object, enum usage why)
{...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 cachecache-list[i] cache-list[--cache-used];// atomic because may collide with concurrent ACQUIREOSAtomicDecrement32Barrier(result-threadCount);}break;case CHECK:// do nothingbreak;}return result;}}...
}这里介绍一下SyncCache和SyncCacheItem
typedef struct {SyncData *data; //该缓存条目对应的SyncDataunsigned int lockCount; //该对象在该线程中被加锁的次数
} SyncCacheItem;typedef struct SyncCache {unsigned int allocated; //该缓存此时对应的缓存大小unsigned int used; //该缓存此时对应的已使用缓存大小SyncCacheItem list[0]; //SyncCacheItem数组
} SyncCache;SyncCacheItem用来记录某个SyncData在某个线程中被加锁的记录一个SyncData可以被多个SyncCacheItem持有SyncCache用来记录某个线程中所有SyncCacheItem并且记录了缓存大小以及已使用缓存大小
全局哈希表查找
快速、慢速流程都没找到缓存就会来到这步——在系统保存的哈希表进行链式查找
static SyncData* id2data(id object, enum usage why)
{...lockp-lock();{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 RELEASEOSAtomicIncrement32Barrier(result-threadCount);goto done;}if ( (firstUnused NULL) (p-threadCount 0) )firstUnused p;}// no SyncData currently associated with objectif ( (why RELEASE) || (why CHECK) )goto done;// an unused one was found, use itif ( firstUnused ! NULL ) {result firstUnused;result-object (objc_object *)object;result-threadCount 1;goto done;}}...
}lockp-lock()并不是在底层对锁进行了封装而是在查找过程前后进行了加锁操作for循环遍历链表如果有符合的就goto done寻找链表中未使用的SyncData并作标记如果是RELEASE或CHECK直接goto done如果第二步中有发现第一次使用的的对象就将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.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_KEYSif (!fastCacheOccupied) {// Save in fast thread cachetls_set_direct(SYNC_DATA_DIRECT_KEY, result);tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);} else
#endif{// Save in thread cacheif (!cache) cache fetch_cache(YES);cache-list[cache-used].data result;cache-list[cache-used].lockCount 1;cache-used;}}...
}第三步情况均不满足即链表不存在——对象对于全部线程来说是第一次加锁就会创建SyncData并存在result里方便下次进行存储done分析 先将前面的lock锁解开 如果是RELEASE类型直接返回nil 对ACQUIRE类型和对象的断言判断 !fastCacheOccupied分支表示支持快速缓存且快速缓存被占用了将该SyncCacheItem数据写入快速缓存中 否则将该SyncCacheItem存入该线程对应的SyncCache中
疑难解答
不能使用非OC对象作为加锁条件——id2data中接收参数为id类型多次锁同一个对象会有什么后果吗——会从高速缓存中拿到data所以只会锁一次对象都说synchronized性能低——是因为在底层增删改查消耗了大量性能加锁对象不能为nil否则加锁无效不能保证线程安全
- (void)test {_testArray [NSMutableArray array];for (int i 0; i 200000; i) {dispatch_async(dispatch_get_global_queue(0, 0), ^{synchronized (self.testArray) {self.testArray [NSMutableArray array];}});}
}上面代码一运行就会崩溃原因是因为在某一瞬间testArray释放了为nil但哈希表中存的对象也变成了nil导致synchronized无效化
解决方案
对self进行同步锁这个似乎太臃肿了使用NSLock
NSLock
使用
NSLock是对互斥锁的简单封装使用如下
- (void)test {self.testArray [NSMutableArray array];NSLock *lock [[NSLock alloc] init];for (int i 0; i 200000; i) {dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];self.testArray [NSMutableArray array];[lock unlock];});}
}NSLock在AFNetworking的AFURLSessionManager.m中有使用到 想要了解一下NSLock的底层原理但发现其是在未开源的Foundation源码下面的但但是Swift对Foundation却开源了可以在swift-corelibs-foundation下载到源码来一探究竟
注意事项
使用互斥锁NSLock异步并发调用block块block块内部递归调用自己问打印什么
- (void)test {NSLock *lock [[NSLock alloc] init];dispatch_async(dispatch_get_global_queue(0, 0), ^{static void (^block)(int);block ^(int value) {NSLog(加锁前);[lock lock];NSLog(加锁后);if (value 0) {NSLog(value——%d, value);block(value - 1);}[lock unlock];};block(10);});
}输出结果并没有按代码表面的想法去走而是只打印了一次value值 原因 互斥锁在递归调用时会造成堵塞并非死锁——这里的问题是后面的代码无法执行下去
第一次加完锁之后还没出锁就进行递归调用第二次加锁就堵塞了线程因为不会查询缓存 解决方案 使用递归锁NSRecursiveLock替换NSLock
NSRecursiveLock
使用
NSRecursiveLock使用和NSLock类似如下代码就能解决上个问题
- (void)test {NSRecursiveLock *lock [[NSRecursiveLock alloc] init];dispatch_async(dispatch_get_global_queue(0, 0), ^{static void (^block)(int);block ^(int value) {[lock lock];if (value 0) {NSLog(value——%d, value);block(value - 1);}[lock unlock];};block(10);});
}NSRecursiveLock在YYKit中YYWebImageOperation.m中有用到
注意事项
递归锁在使用时需要注意死锁问题——前后代码相互等待便会产生死锁
NSCondition
NSCondition是一个条件锁可能平时用的不多但与信号量相似线程1需要等到条件1满足才会往下走否则就会堵塞等待直至条件满足
同样的能在Swift源码中找到关于NSCondition部分
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)}deinit {pthread_mutex_destroy(mutex)pthread_cond_destroy(cond)}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}open var name: String?
}从上述精简后的代码可以得出以下几点
NSCondition是对mutex和cond的一种封装cond就是用于访问和操作特定类型数据的指针wait操作会阻塞线程使其进入休眠状态直至超时signal操作是唤醒一个正在休眠等待的线程broadcast会唤醒所有正在等待的线程
NSConditionLock
顾名思义就是NSCondition Lock 那么和NSCondition的区别在于哪里呢接下来看一下NSConditionLock源码
open class NSConditionLock : NSObject, NSLocking {internal var _cond NSCondition()internal var _value: Intinternal 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 {_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是NSCondition加线程数的封装NSConditionLock可以设置锁条件而NSCondition只是无脑的通知信号
os_unfair_lock
由于OSSpinLock自旋锁的bug替代方案是内部封装了os_unfair_lock而os_unfair_lock在加锁时会处于休眠状态而不是自旋锁的忙等状态
总结
OSSpinLock不再安全底层用os_unfair_lock替代atomic只能保证setter、getter时线程安全所以更多的使用nonatomic来修饰读写锁更多使用栅栏函数来实现synchronized在底层维护了一个哈希链表进行data的存储使用recursive_mutex_t进行加锁NSLock、NSRecursiveLock、NSCondition和NSConditionLock底层都是对pthread_mutex的封装NSCondition和NSConditionLock是条件锁当满足某一个条件时才能进行操作和信号量dispatch_semaphore类似普通场景下涉及到线程安全可以用NSLock循环调用时用NSRecursiveLock循环调用且有线程影响时请注意死锁如果有死锁问题请使用synchronized