线程安全+13种锁
线程安全
- 为什么要线程安全
- 自旋锁和互斥锁
- 锁的类型(13种锁) 1. OSSpinLock2. os_unfair_lock3. pthread_mutex4. dispatch_semaphore5. dispatch_queue(DISPATCH_QUEUE_SERIAL)6. NSLock7. NSRecursiveLock8. NSCondition9. NSConditionLock10. @synchronized11. pthread_rwlock12. dispatch_barrier_async13. atomic
- 锁的性能比较
1. 为什么要线程安全
多个线程访问同一块资源的时候,很容易引发数据混乱,一个大家都喜欢拿来举的例子就是
卖火车票
,今天我使用这个案例,
假设有100张票,同时开5个窗口卖票,看看结果如何
//卖票演示-(void)ticketTest{self.ticketsCount =50;
dispatch_queue_t queue =dispatch_get_global_queue(0,0);for(NSInteger i =0; i <5; i++){dispatch_async(queue,^{for(int i =0; i <10; i++){[self sellingTickets];}});}}//卖票-(void)sellingTickets{int oldMoney =self.ticketsCount;sleep(.2);
oldMoney -=1;self.ticketsCount = oldMoney;NSLog(@"当前剩余票数-> %d", oldMoney);}
正常情况下我有50张票,然后卖了50次,剩余票数应该为0,但是打印结果是3,所以这里就存在了线程安全问题。
出现线程安全的原理
出现线程安全的原因就是在同一个时间,多个线程同时读取一个值,像线程A和B同时读取了当前票数为10,等于是卖了两张票,但是总票数其实就减少了一张。
解决方法
使用线程同步计数,按照预定的先后次序依次进行,常见的线程同步计数就是
加锁
2. 自旋锁和互斥锁
自旋锁(Spin lock)
自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就会一直循环在那里看是否该自旋锁的保持者已经释放了锁(轮询),“自旋”一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。虽然它的效率比互斥锁高,单一它也有些不足之处:
- 自旋锁会一直占用CPU,他在未获得锁的情况下,一直运行 — 自旋,所以占用着CPU,如果不能在很短时间内获得锁,这无疑会使CPU效率降低
- 在用自旋锁时很可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如copy_to_user()、copy_from_user()、kmalloc()等。 因此我们要慎重使用自旋锁,自旋锁只有在内核抢占式或SMP等情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空。自旋锁适用于锁保持着保持锁时间比较短的情况下。
互斥锁
互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和Core1上(Core是处理器核心)。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正在被线程B锁持有,那么线程A就会被阻塞(blocking),Core0会在此时进行上下文切换(Contex Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而自旋锁则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在Core0上进行忙等待,并不停的动进行锁清秋,直到得到这个锁为止。互斥锁用于持锁时间比较长的操作
两种锁的加锁原理
互斥锁:线程会从sleep(加锁)-> running(解锁), 过程中有上下文的切换,cpu的抢占,信号的发送等开销。
自旋锁:线程一直是running(加锁->解锁),死循环检测锁的标志位(轮询),机制不复杂。
对比
互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu。起始开销虽然低于互斥锁,但随着持锁时间编程,加锁的开销是线程增长。
两种锁的应用
临界区 : 临界区是一段访问共享资源的代码,在同一时刻只能有一个线程/进程进入执行。如果有多个线程/进程同时进入,可能会导致数据竞争和不一致的问题。
互斥锁 :用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
- 单核处理器
自旋锁:就主要用于临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的处理器。
3. 13种锁
1. OSSpinLock (已弃用)
OSSpinLock叫做“自旋锁”,使用时需要导入头文件#import <libkern/OSAtomic.h>
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;//加锁OSSpinLockLock(&lock);//解锁OSSpinLockUnlock(&lock);
OSSpinLock在iOS10.0以后就被弃用了,可以使用os_unfair_lock_lock替代。而且还有一些安全性问题,
不再安全的OSSpinLock
2. os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock,从iOS开始才支持。从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,而并非忙等。使用需要导入头文件#import<os/lock.h>
//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;//加锁os_unfair_lock_lock(&lock);//解锁os_unfair_lock_unlock(&lock);
demo
#import"os_unfair_lockDemo.h"#import<os/lock.h>@interfaceos_unfair_lockDemo()@property(assign, nonatomic) os_unfair_lock ticketLock;@end@implementation os_unfair_lockDemo
-(instancetype)init
{self=[super init];if(self){self.ticketLock = OS_UNFAIR_LOCK_INIT;}returnself;}//卖票-(void)sellingTickets{os_unfair_lock_lock(&_ticketLock);[super sellingTickets];os_unfair_lock_unlock(&_ticketLock);}@end
3.pthread_mutex
mutex叫做“互斥锁”,等待锁的线程会处于休眠状态。需要导入头文件#import <pthread.h>
使用步骤:
- 初始化锁的属性
pthread_mutexattr_t attr;// 定义一个(锁的)属性 pthread_mutexattr_init(&attr);// 初始化这个属性 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);// 设置属性类型 /*
* Mutex type attributes
*/#definePTHREAD_MUTEX_NORMAL0//默认互斥锁 #definePTHREAD_MUTEX_ERRORCHECK1//这种类型互斥锁提供了错误检查功能 #definePTHREAD_MUTEX_RECURSIVE2// 支持递归的互斥锁 #definePTHREAD_MUTEX_DEFAULTPTHREAD_MUTEX_NORMAL
- PTHREAD_MUTEX_NORMAL: - 这是 POSIX 互斥锁的默认类型。- 当一个线程尝试获取已被另一个线程持有的锁时,该线程会阻塞。- 如果一个线程尝试重复获取已被自己持有的锁,行为是未定义的。这可能会导致死锁。
- PTHREAD_MUTEX_ERRORCHECK: - 这种类型的互斥锁提供了错误检查功能。- 当一个线程尝试获取已被自己持有的锁时,该函数会返回一个错误。- 这有助于避免因重复获取同一锁而导致的死锁问题。
- PTHREAD_MUTEX_RECURSIVE: - 这种类型的互斥锁支持递归获取。- 当一个线程已经持有该锁时,它可以再次获取该锁而不会被阻塞。- 线程需要释放同等次数的锁,才能使得该锁完全释放。
- PTHREAD_MUTEX_DEFAULT: - 这是互斥锁的默认类型,等同于 PTHREAD_MUTEX_NORMAL。- 当没有显式指定互斥锁类型时,系统会使用这种默认类型。
- (用属性)初始化锁
// 初始化锁pthread_mutex_init(mutex,&attr);
- 初始化锁结束以后,销毁属性
// 销毁属性pthread_mutexattr_destroy(&attr);
- 加锁解锁
pthread_mutex_lock(&_mutex);pthread_mutex_unlock(&_mutex);
- 销毁锁
pthread_mutex_destroy(&_mutex);
备注:我们可以不初始化属性,在传属性的时候直接传
NULL
,表示使用默认属性
PTHREAD_MUTEX_NORMAL
。
pthread_mutex_init(mutex, NULL);
具体代码
#import"pthread_mutexDemo.h"#import<pthread.h>@interfacepthread_mutexDemo()@property(assign, nonatomic) pthread_mutex_t ticketMutex;@end@implementation pthread_mutexDemo
-(instancetype)init
{self=[super init];if(self){// 初始化属性
pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);// 初始化锁pthread_mutex_init(&(_ticketMutex),&attr);// 销毁属性pthread_mutexattr_destroy(&attr);}returnself;}//卖票-(void)sellingTickets{pthread_mutex_lock(&_ticketMutex);[super sellingTickets];pthread_mutex_unlock(&_ticketMutex);}@end
死锁 : 我们稍微修改一下代码
//卖票-(void)sellingTickets{pthread_mutex_lock(&_ticketMutex);[super sellingTickets];[self sellingTickets2];pthread_mutex_unlock(&_ticketMutex);}-(void)sellingTickets2{pthread_mutex_lock(&_ticketMutex);NSLog(@"%s",__func__);pthread_mutex_unlock(&_ticketMutex);}
上面的代码就会造成线程死锁。由于 _ticketMutex已经被当前线程持有,根据 PTHREAD_MUTEX_NORMAL 类型的锁语义, 当线程再次尝试获取已被自己持有的锁时,会发生死锁。
解决方法:
- 使用的
pthread_mutex_t
的属性PTHREAD_MUTEX_RECURSIVE
- 使用的
PTHREAD_MUTEX_RECURSIVE
递归锁:允许同一个线程对同一把锁进行重复加锁。
要考重点同一个线程和同一把锁
- 在方法sellingTickets2中重新再创建一把新的锁,两个方法的锁对象不同,就不会造成线程死锁了。(也就是不使用同一把锁)
- 让方法sellingTickets2在另一个线程上运行。(不在同一个线程加锁)。但是这样会导致代码逻辑与之前不同了。
-(void)__saleTicket {pthread_mutex_lock(&_ticketMutex);[super __saleTicket];[self performSelectorInBackground:@selector(__saleTicket2) withObject:nil];pthread_mutex_unlock(&_ticketMutex);}-(void)__saleTicket2 {pthread_mutex_lock(&_ticketMutex);NSLog(@"%s",__func__);pthread_mutex_unlock(&_ticketMutex);}
条件
// 初始化属性
pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);// 初始化锁pthread_mutex_init(&_mutex,&attr);// 销毁属性pthread_mutexattr_destroy(&attr);// 初始化条件
pthread_cond_t condition
pthread_cond_init(&_cond,NULL);// 等待条件pthread_cond_wait(&_cond,&_mutex);//激活一个等待该条件的线程pthread_cond_signal(&_cond);//激活所有等待该条件的线程pthread_cond_broadcast(&_cond);//销毁资源pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);
使用案例:假设我们有一个数组,里面有两个线程,一个是添加数组,一个是删除数组,我们先调用删除数组,再调用添加数组,但是在数组为空的时候不调用删除数组。
#import"pthread_mutexDemo1.h"#import<pthread.h>@interfacepthread_mutexDemo1()@property(assign, nonatomic) pthread_mutex_t mutex;@property(assign, nonatomic) pthread_cond_t cond;@property(strong, nonatomic) NSMutableArray *data;@end@implementation pthread_mutexDemo1
-(instancetype)init
{if(self=[super init]){// 初始化属性
pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);// 初始化锁pthread_mutex_init(&_mutex,&attr);// 销毁属性pthread_mutexattr_destroy(&attr);// 初始化条件pthread_cond_init(&_cond,NULL);self.data =[NSMutableArray array];}returnself;}-(void)otherTest
{[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];}// 线程1// 删除数组中的元素-(void)__remove
{pthread_mutex_lock(&_mutex);NSLog(@"__remove - begin");if(self.data.count ==0){// 等待pthread_cond_wait(&_cond,&_mutex);}[self.data removeLastObject];NSLog(@"删除了元素");pthread_mutex_unlock(&_mutex);}// 线程2// 往数组中添加元素-(void)__add
{pthread_mutex_lock(&_mutex);sleep(1);[self.data addObject:@"Test"];NSLog(@"添加了元素");// 激活一个等待该条件的线程pthread_cond_signal(&_cond);pthread_mutex_unlock(&_mutex);}-(void)dealloc
{pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}
- pthread_cond_wait(&_cond, &_mutex):- 作用: - 该函数用于当前线程等待一个条件变量 (_cond) 的信号。- 在调用该函数时,当前线程会释放关联的互斥锁 (_mutex),并进入休眠状态,直到另一个线程发出条件变量的信号。- 用法: - 在调用该函数之前,必须先获取关联的互斥锁 (_mutex)。- 当条件变量被信号唤醒时,线程会重新获取互斥锁 (_mutex),然后继续执行后续的代码。
- pthread_cond_signal(&_cond):- 作用: - 该函数用于向一个条件变量 (_cond) 发出信号,通知等待该条件变量的一个线程可以继续执行。- 如果有多个线程正在等待该条件变量,则只唤醒其中一个线程。- 用法: - 通常在完成某些操作后,或者满足某些条件时,使用该函数向等待的线程发出信号。- 在调用该函数之前,必须先获取关联的互斥锁 (_mutex)。
4. NSLock
NSLock是对mutex普通锁的封装。pthread_mutex_init(mutex, NULL);
NSLock 遵循 NSLocking 协议。
lock
方法是加锁,
unlock
是解锁,
tryLock
是尝试加锁,如果失败的话返回 NO,
lockBeforeDate:
是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。
@protocol NSLocking
-(void)lock;-(void)unlock;@end@interface NSLock : NSObject <NSLocking>{@privatevoid*_priv;}-(BOOL)tryLock;-(BOOL)lockBeforeDate:(NSDate *)limit;@property(nullable, copy) NSString *name
@end
使用起来也是十分的简单
#import"LockDemo.h"@interfaceLockDemo()@property(strong, nonatomic) NSLock *ticketLock;@end@implementation LockDemo
//卖票-(void)sellingTickets{[self.ticketLock lock];[super sellingTickets];[self.ticketLock unlock];}@end
5. NSRecursiveLock
NSRecursiveLock是对
mutex递归锁
的封装,API跟NSLock基本一致
#import"RecursiveLockDemo.h"@interfaceRecursiveLockDemo()@property(nonatomic,strong) NSRecursiveLock *ticketLock;@end@implementation RecursiveLockDemo
//卖票-(void)sellingTickets{[self.ticketLock lock];[super sellingTickets];[self.ticketLock unlock];}@end
6. NSCondition
NSCondition
是对
mutex
和
cond
的封装,更加面向对象,我们使用起来也更加的方便简洁
@interface NSCondition : NSObject <NSLocking>{-(void)wait;-(BOOL)waitUntilDate:(NSDate *)limit;-(void)signal;-(void)broadcast;@property(nullable, copy) NSString *name
@end
对于上面那个数组操作的案例我们就可以变成这个样子了
// 线程1// 删除数组中的元素-(void)__remove
{[self.condition lock];if(self.data.count ==0){// 等待[self.condition wait];}[self.data removeLastObject];NSLog(@"删除了元素");[self.condition unlock];}// 线程2// 往数组中添加元素-(void)__add
{[self.condition lock];sleep(1);[self.data addObject:@"Test"];NSLog(@"添加了元素");// 信号[self.condition signal];[self.condition unlock];}
7. NSConditionLock
NSConditionLock
是对
NSCondition
的进一步封装,可以设置具体的条件值
@interface NSConditionLock : NSObject <NSLocking>{-(instancetype)initWithCondition:(NSInteger)condition;@property(readonly) NSInteger condition;-(void)lockWhenCondition:(NSInteger)condition;-(BOOL)tryLock;-(BOOL)tryLockWhenCondition:(NSInteger)condition;-(void)unlockWithCondition:(NSInteger)condition;-(BOOL)lockBeforeDate:(NSDate *)limit;-(BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;@property(nullable, copy) NSString *name;@end
里面有三个常用的方法
- initWithCondition: 初始化Condition,并且设置状态值
- lockWhenCondition:(NSInteger)condition: 当前状态值为condition的时候加锁
- unLockWithCondition:(NSInterger)condition解锁,并将状态值设置为condition
@interfaceNSConditionLockDemo()@property(strong, nonatomic) NSConditionLock *conditionLock;@end@implementation NSConditionLockDemo
-(instancetype)init
{if(self=[super init]){self.conditionLock =[[NSConditionLock alloc] initWithCondition:1];}returnself;}-(void)otherTest
{[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];}-(void)__one
{[self.conditionLock lock];NSLog(@"__one");sleep(1);[self.conditionLock unlockWithCondition:2];}-(void)__two
{[self.conditionLock lockWhenCondition:2];NSLog(@"__two");[self.conditionLock unlockWithCondition:3];}@end
8.dispatch_semaphore
- semaphore叫做“信号量”,设计目的是用来控制对共享资源的访问,确保资源不会被过度使用。
- 信号量的初始值可以用来控制线程并发访问的最大数量。
- 信号量的初始值为1,代表同时只允许一条线程访问资源,保证线程同步。
- 信号量的值不能超过它的初始值。信号量的初始值代表了系统中可用的某种资源的数量。当线程获取资源时,信号量的值会减少;当线程释放资源时,信号量的值会增加。
//表示最多开启5个线程dispatch_semaphore_create(5);// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);// 让信号量的值+1dispatch_semaphore_signal(self.semaphore);
@interfacedispatch_semaphoreDemo()@property(strong, nonatomic) dispatch_semaphore_t semaphore;@end@implementation dispatch_semaphoreDemo
-(instancetype)init
{if(self=[super init]){self.semaphore =dispatch_semaphore_create(1);//信号量初始值为1,相当于锁 }returnself;}-(void)otherTest
{for(int i =0; i <20; i++){[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];}}-(void)test
{//dispatch_semaphore_wait:// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);sleep(2);NSLog(@"test - %@",[NSThread currentThread]);// 让信号量的值+1dispatch_semaphore_signal(self.semaphore);}@end
我们在运行代码打印的时候发现,每隔一秒出现一次打印。虽然我们同时开启20个线程,但是一次只能访问一条线程的资源。
9. dispatch_queue
使用GCD的串行队列也可以实现线程同步
dispatch_queue_t queue =dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);dispatch_sync(queue,^{// 追加任务1for(int i =0; i <2;++i){NSLog(@"1---%@",[NSThread currentThread]);}});dispatch_sync(queue,^{// 追加任务2for(int i =0; i <2;++i){NSLog(@"2---%@",[NSThread currentThread]);}});
10. @synchronized
@synchronized
是对
mutex
递归锁的封装,
@synchronized(obj)
内部会生成obj对应的递归锁,然后进行加锁、解锁操作
//卖票-(void)sellingTickets{@synchronized([self class]){[super sellingTickets];}}
对于实现底层我们可以在objc4的objc-sync.mm文件中找到synchronized就是在开始和结束的时候调用了objc_sync_enter & objc_sync_exit方法
objc_sync_enter实现
intobjc_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;}
就是根据id2data方法找到一个data对象,然后在对data对象进行mutex.lock()加锁操作。我们点击进入id2data方法继续查找
#defineLIST_FOR_OBJ(obj) sDataLists[obj].datastatic StripedMap<SyncList> sDataLists;
发现获取data对象的方法其实就是根据sDataLists[obj].data这个方法来实现的,也就是一个哈希表。
@synchronized详解
11. atomic
- atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
- 可以参考源码objc4的objc-accessors.mm
- 它并不能保证属性的过程是线程安全的
12. pthread_rwlock:读写锁
pthread_rwlock经常用于文件等数据的读写操作,需要导入头文件#import <pthread.h>
iOS中的读写安全方案需要注意一下场景,即多读单写
- 同一时间,只能有1个线程进行写的操作
- 同一时间,允许有多个线程进行读的操作
- 同一时间,不允许既有写的操作,由有读的操作
//初始化锁
pthread_rwlock_t lock;pthread_rwlock_init(&_lock,NULL);//读加锁pthread_rwlock_rdlock(&_lock);//读尝试加锁pthread_rwlock_trywrlock(&_lock)//写加锁pthread_rwlock_wrlock(&_lock);//写尝试加锁pthread_rwlock_trywrlock(&_lock)//解锁pthread_rwlock_unlock(&_lock);//销毁pthread_rwlock_destroy(&_lock);
#import<pthread.h>@interfacepthread_rwlockDemo()@property(assign, nonatomic) pthread_rwlock_t lock;@end@implementation pthread_rwlockDemo
-(instancetype)init
{self=[super init];if(self){// 初始化锁pthread_rwlock_init(&_lock,NULL);}returnself;}-(void)otherTest{
dispatch_queue_t queue =dispatch_get_global_queue(0,0);for(int i =0; i <10; i++){dispatch_async(queue,^{[self read];});dispatch_async(queue,^{[self write];});}}-(void)read {pthread_rwlock_rdlock(&_lock);sleep(1);NSLog(@"%s",__func__);pthread_rwlock_unlock(&_lock);}-(void)write
{pthread_rwlock_wrlock(&_lock);sleep(1);NSLog(@"%s",__func__);pthread_rwlock_unlock(&_lock);}-(void)dealloc
{pthread_rwlock_destroy(&_lock);}@end
我们可以发现读操作1s有可能出现多次,但是写操作不会。
13. dispatch_barrier_async
这个函数传入的并发队列必须是自己通过dispatch_queue_creat创建的,如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
//初始化self.queue =dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);//读操作dispatch_async(self.queue,^{});//写操作dispatch_barrier_async(self.queue,^{});
锁的性能比较
性能由高到低排序:
1、os_unfair_lock (重点看)
2、OSSpinLock(弃用)(重点看)
3、dispatch_semaphore (重点看)
4、pthread_mutex (重点看)
5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
6、NSLock(对mutex普通锁的封装)
7、NSCondition (对mutex和cond的封装,更加面向对象)
8、pthread_mutex(recursive)(重点看)
9、NSRecursiveLock (对mutex递归锁的封装)
10、NSConditionLock (对NSCondition的进一步封装)
11、@synchronized (对mutex递归锁的封装)
版权归原作者 cheng_lin0201 所有, 如有侵权,请联系我们删除。