iOS底层-多线程(二)
iOS中的线程同步方案
-
将买卖票和存取钱封装到一个基类中
#import <Foundation/Foundation.h> @interface ZHBaseDemo : NSObject /** 存取钱演示 */ -(void)moneyTest; /** 存钱 */ -(void)saveMoney; /** 取钱 */ -(void)drawMoney; /** 卖一张票 */ -(void)saleTicket; /** 卖多张票 */ -(void)saleTicketTest; @end #import "ZHBaseDemo.h" @interface ZHBaseDemo () @property (nonatomic, assign) int ticketsCount; @property (nonatomic, assign) int money; @end @implementation ZHBaseDemo /** 存取钱演示 */ -(void)moneyTest{ self.money = 100; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ for (int i =0; i<3; i++) { [self saveMoney]; } }); dispatch_async(queue, ^{ for (int i =0; i<3; i++) { [self drawMoney]; } }); } /** 存钱 */ -(void)saveMoney{ int oldMoney = self.money; sleep(.2); oldMoney+= 50; self.money = oldMoney; NSLog(@"存50,还剩%d元====%@",oldMoney,[NSThread currentThread]); } /** 取钱 */ -(void)drawMoney{ int oldMoney = self.money; sleep(.2); oldMoney-= 20; self.money = oldMoney; NSLog(@"取20,还剩%d元====%@",oldMoney,[NSThread currentThread]); } /** 卖一张票 */ -(void)saleTicket{ int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"还剩%d张票====%@",oldTicketsCount,[NSThread currentThread]); } /** 卖多张票 */ -(void)saleTicketTest{ self.ticketsCount = 15; dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i =0; i<2; i++) { [self saleTicket]; } }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i =0; i<2; i++) { [self saleTicket]; } }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i =0; i<2; i++) { [self saleTicket]; } }); } @end
os_unfair_lock
- os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
- 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
- 需要导入头文件#import <os/lock.h>
-
代码举例:
#import "ZHBaseDemo.h" @interface OSUnfairLockDemo : ZHBaseDemo @end #import "OSUnfairLockDemo.h" #import <os/lock.h> @interface OSUnfairLockDemo () @property (nonatomic, assign) os_unfair_lock moneyLock; @property (nonatomic, assign) os_unfair_lock ticketLock1; @end @implementation OSUnfairLockDemo - (instancetype)init{ self = [super init]; if (self) { //初始化 self.moneyLock = OS_UNFAIR_LOCK_INIT; self.ticketLock1 = OS_UNFAIR_LOCK_INIT; } return self; } -(void)saleTicket{ //尝试加锁,已经加锁就不走,没有加锁就加锁 if (os_unfair_lock_trylock(&_ticketLock1)) { [super saleTicket]; //解锁 os_unfair_lock_unlock(&_ticketLock1); } } -(void)drawMoney{ //加锁 os_unfair_lock_lock(&_moneyLock); [super drawMoney]; //解锁 os_unfair_lock_unlock(&_moneyLock); } -(void)saveMoney{ //加锁 os_unfair_lock_lock(&_moneyLock); [super saveMoney]; //解锁 os_unfair_lock_unlock(&_moneyLock); } @end
pthread_mutex
- 凡是以pthread开头的基本上都是跨平台使用的
- mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
- 需要导入头文件#import
-
基本用法示例
#import "PthreadMutexLockDemo.h" #import <pthread.h> @interface PthreadMutexLockDemo () @property (nonatomic, assign) pthread_mutex_t moneyLock; @property (nonatomic, assign) pthread_mutex_t ticketLock1; @property (nonatomic, assign) pthread_mutex_t mutexLock; @end /** //Mutex type attributes #define PTHREAD_MUTEX_NORMAL 0 #define PTHREAD_MUTEX_ERRORCHECK 1 #define PTHREAD_MUTEX_RECURSIVE 2 //递归锁 #define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL */ @implementation PthreadMutexLockDemo -(void)__initMutex:(pthread_mutex_t *)mutex{ //初始化锁的属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); //初始化锁 pthread_mutex_init(mutex, &attr); //销毁相关资源 // pthread_mutexattr_destroy(&attr); // pthread_mutex_destroy(mutex); } - (instancetype)init { self = [super init]; if (self) { [self __initMutex:&_moneyLock]; [self __initMutex:&_ticketLock1]; [self __initMutex:&_mutexLock]; } return self; } -(void)saleTicket{ //尝试加锁,已经加锁就不走,没有加锁就加锁 if (pthread_mutex_trylock(&_ticketLock1)) { [super saleTicket]; //解锁 pthread_mutex_unlock(&_ticketLock1); } } -(void)drawMoney{ //加锁 pthread_mutex_lock(&_moneyLock); [super drawMoney]; //解锁 pthread_mutex_unlock(&_moneyLock); } -(void)saveMoney{ //加锁 pthread_mutex_lock(&_moneyLock); [super saveMoney]; //解锁 pthread_mutex_unlock(&_moneyLock); } //这里需要销毁锁 -(void)dealloc{ pthread_mutex_destroy(&_ticketLock1); pthread_mutex_destroy(&_moneyLock); } @end
- pthread_mutex之递归锁
-
下面的死锁现象
/*********死锁现象分析**********/ -(void)otherTest{ //加锁 pthread_mutex_lock(&_mutexLock); //只有_mutexLock的锁解开才能执行otherTest2中的代码 [self otherTest2]; NSLog(@"==========%s",__func__); //但是要解锁必须otherTest2执行完成,因此造成死锁 //解锁 pthread_mutex_unlock(&_mutexLock); } -(void)otherTest2{ //进来发现_mutexLock已经锁住了,所以无法进行下去,等待 //加锁 pthread_mutex_lock(&_mutexLock); NSLog(@"==========%s",__func__); //解锁 pthread_mutex_unlock(&_mutexLock); }
- otherTest将_mutexLock锁住,内部调用otherTest2
- 但是otherTest2也是用的_mutexLock锁,发现锁住了,那就线程等待,不能继续执行,一直等到_mutexLock锁打开为止。
- otherTest的解锁在otherTest2执行完之后才能开锁,因此造成了死锁现象
- 解决办法
- 这2个方法用不同的锁即可
-
疑问,如果是递归呢? 怎么办呢
-(void)otherTest{ //加锁 pthread_mutex_lock(&_mutexLock); static int count = 0; count ++; //只有_mutexLock的锁解开才能执行otherTest if(count >10) return; [self otherTest]; //一次也不会执行 NSLog(@"==========%s",__func__); //解锁 pthread_mutex_unlock(&_mutexLock); }
-
递归锁
-
将__initMutex这个方法中的下面这个属性改成递归属性,就是一把递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
- 这样就可以解决上面的问题了。
- 那么问题来了,递归锁既然可以重复加锁,那么当多个线程访问的时候,那岂不是又不安全了?
- 递归锁的原理: 允许同一个线程对一把锁进行重复加锁
- 即当多个线程来访问时,不同的线程是不能重复加锁的,仍然是安全的
-
-
- pthread_mutex – 条件
-
代码举例:
#import "PthreadMutexLockDemo2.h" #import <pthread.h> @interface PthreadMutexLockDemo2 () @property (nonatomic, assign) pthread_mutex_t mutexLock; @property (nonatomic,strong) NSMutableArray *data; @property (nonatomic, assign) pthread_cond_t cond; @end @implementation PthreadMutexLockDemo2 - (instancetype)init { self = [super init]; if (self) { //初始化锁的属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); //递归锁 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //初始化锁 pthread_mutex_init(&_mutexLock, &attr); self.data = [NSMutableArray array]; //初始化条件 //NULL代表使用默认属性 pthread_cond_init(&_cond, NULL); } return self; } //删除数组中的元素 -(void)__remove{ //加锁 pthread_mutex_lock(&_mutexLock); NSLog(@"------remove"); //一旦发现当前数组无元素就开始等待,让当前线程休眠。但是同时把锁打开,允许别的线程使用这把锁加锁 if (self.data.count == 0) { //等待条件(进入休眠,放开_mutexLock锁;被唤醒后,会再次对_mutexLock锁住) pthread_cond_wait(&_cond, &_mutexLock); } [self.data removeLastObject]; NSLog(@"删除了元素"); //解锁 pthread_mutex_unlock(&_mutexLock); } //往数组中添加元素 -(void)__add{ //加锁 pthread_mutex_lock(&_mutexLock); NSLog(@"------add"); [self.data addObject:@"test"]; NSLog(@"添加了元素"); //激活一个等待该条件的线程 //发信号通知等待(_cond),不用等了,唤醒等待的线程,让它继续执行 pthread_cond_signal(&_cond); //激活所有等待该条件的线程 // pthread_cond_broadcast(&_cond); //解锁 pthread_mutex_unlock(&_mutexLock); } -(void)otherTest{ [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start]; sleep(1); [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start]; } //这里需要销毁锁 -(void)dealloc{ pthread_mutex_destroy(&_mutexLock); pthread_cond_destroy(&_cond); } @end
- 分析流程
- 线程1先进入
__remove
,但是发现data
没有数据 - 让线程1等待,进入休眠状态,不继续执行,同时放开
_mutexLock
锁 - 此时线程2进入
__add
,_mutexLock
上锁(因为线程1已经给他放开了),执行addobject
- 执行完成,唤醒线程1(此时线程2还没有开锁,需要等着线程2开完锁),线程2
_mutexLock
解锁,线程1_mutexLock
加锁,继续执行删除remove
- 线程1执行
remove
完毕,_mutexLock
开锁
- 线程1先进入
-
测试
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ PthreadMutexLockDemo2 *demo3 = [[PthreadMutexLockDemo2 alloc] init]; [demo3 otherTest]; }
-
打印:
------remove ------add 添加了元素 删除了元素
- 可以看到尽管先执行了remove方法,但是最终停下了了
- 去执行了add,执行完,才返回执行删除
- 分析流程
-
使用场合:
- 某个线程执行完之后当前线程再执行
-
NSLock、NSRecursiveLock
-
NSLock是对mutex普通锁的封装
@protocol NSLocking //加锁 - (void)lock; //解锁 - (void)unlock; @end @interface NSLock : NSObject <NSLocking> { @private void *_priv; } //试着加锁(不等待) - (BOOL)tryLock; //等待limit时间,时间内锁打开就加锁,到limit时间了,还没有开锁那就不加了,返回加锁失败 - (BOOL)lockBeforeDate:(NSDate *)limit; @property (nullable, copy) NSString *name; @end
- NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致
-
举例:
#import "NSLockDemo.h" @interface NSLockDemo () @property (nonatomic, strong) NSLock *moneyLock; @property (nonatomic, strong) NSLock *ticketLock1; @end @implementation NSLockDemo - (instancetype)init { self = [super init]; if (self) { self.moneyLock = [[NSLock alloc] init]; self.ticketLock1 = [[NSLock alloc] init]; } return self; } -(void)saleTicket{ //尝试加锁,已经加锁就不走,没有加锁就加锁 if ([self.ticketLock1 tryLock]) { [super saleTicket]; //解锁 [self.ticketLock1 unlock]; } } -(void)drawMoney{ //加锁 [self.moneyLock lock]; [super drawMoney]; //解锁 [self.moneyLock unlock]; } -(void)saveMoney{ //加锁 [self.moneyLock lock]; [super saveMoney]; //解锁 [self.moneyLock unlock]; } @end
- NSRecursiveLock不在举例,因为跟NSLock用法一样
NSCondition、NSConditionLock
-
NSCondition是对mutex和cond的封装
@interface NSCondition : NSObject <NSLocking> { @private void *_priv; } //等待 - (void)wait; //等待多长时间 - (BOOL)waitUntilDate:(NSDate *)limit; //给等待发送信号 - (void)signal; //给所有等待发送信号 - (void)broadcast; @property (nullable, copy) NSString *name ; @end
-
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
@interface NSConditionLock : NSObject <NSLocking> { @private void *_priv; } - (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER; @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
-
举例使用1(NSCondition)
#import "NSConditionDemo.h" @interface NSConditionDemo () @property (nonatomic, strong) NSCondition *condition; @property (nonatomic,strong) NSMutableArray *data; @end @implementation NSConditionDemo - (instancetype)init { self = [super init]; if (self) { self.data = [NSMutableArray array]; self.condition = [[NSCondition alloc] init]; } return self; } //删除数组中的元素 -(void)__remove{ //加锁 [self.condition lock]; NSLog(@"------remove"); //一旦发现当前数组无元素就开始等待,让当前线程休眠。但是同时把锁打开,允许别的线程使用这把锁加锁 if (self.data.count == 0) { //等待条件(进入休眠,放开_mutexLock锁;被唤醒后,会再次对_mutexLock锁住) [self.condition wait]; } [self.data removeLastObject]; NSLog(@"删除了元素"); //解锁 [self.condition unlock]; } //往数组中添加元素 -(void)__add{ //加锁 [self.condition lock]; NSLog(@"------add"); [self.data addObject:@"test"]; NSLog(@"添加了元素"); //激活一个等待该条件的线程 //发信号通知等待(_cond),不用等了,唤醒等待的线程,让它继续执行 [self.condition signal]; //激活所有等待改条件的线程 [self.condition broadcast]; //解锁 [self.condition unlock]; } -(void)otherTest{ [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start]; sleep(1); [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start]; } @end
-
举例使用2(NSConditionLock)
#import "NSConditionLockDemo.h" @interface NSConditionLockDemo () @property (nonatomic, strong) NSConditionLock *conditionLock; @property (nonatomic,strong) NSMutableArray *data; @end @implementation NSConditionLockDemo - (instancetype)init { self = [super init]; if (self) { self.data = [NSMutableArray array]; //初始化一把锁,而且内部条件值为1 self.conditionLock = [[NSConditionLock alloc] initWithCondition:1]; //默认conditon值为0 // self.conditionLock = [[NSConditionLock alloc] init]; } return self; } //删除数组中的元素 -(void)__one{ NSLog(@"------one-in---%@",[NSThread currentThread]); //加锁 //当conditionLock的内部条件值(condition属性)为1的时候,而且当前锁没有加锁时,才加锁,否则就等待 [self.conditionLock lockWhenCondition:1]; NSLog(@"------one"); //开锁 //设置这把锁的条件值是2,并且把这把锁打开 [self.conditionLock unlockWithCondition:2]; } //往数组中添加元素 -(void)__two{ NSLog(@"------two-in---%@",[NSThread currentThread]); //加锁 //当conditionLock的内部条件值(condition属性)为2的时候才加锁,而且当前锁没有加锁时,才加锁,否则就等待 [self.conditionLock lockWhenCondition:2]; NSLog(@"------two"); //解锁 设置这把锁的条件值是3,并且把这把锁打开 [self.conditionLock unlockWithCondition:3]; } -(void)__three{ NSLog(@"------three-in---%@",[NSThread currentThread]); [self.conditionLock lockWhenCondition:3]; NSLog(@"------three"); //解锁 [self.conditionLock unlock]; } //可以控制让3个县城按一定顺序执行 -(void)otherTest{ [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start]; } @end //测试 NSConditionLockDemo *demo6 = [[NSConditionLockDemo alloc] init]; [demo6 otherTest];
-
打印:
------three-in---<NSThread: 0x60000194dc80>{number = 3, name = (null)} ------two-in---<NSThread: 0x60000194de40>{number = 4, name = (null)} ------one-in---<NSThread: 0x60000194e080>{number = 5, name = (null)} ------one ------two ------three
- 可以发现尽管是线程3先进入__three方法,但是执行的顺序仍然是按照one、two、three
- 因此,NSConditionLock可以控制线程执行的顺序
-
dispatch_queue
- 直接使用GCD的串行队列,也是可以实现线程同步的
- 线程同步本质就是按顺序执行任务,不让多条线程占用一份资源
-
举例使用:
#import "SerialQueueDemo.h" @interface SerialQueueDemo () @property (nonatomic, strong) dispatch_queue_t moneyQueue; @property (nonatomic, strong) dispatch_queue_t ticketQueue; @end @implementation SerialQueueDemo - (instancetype)init { self = [super init]; if (self) { self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL); self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL); } return self; } -(void)saleTicket{ dispatch_sync(self.ticketQueue, ^{ [super saleTicket]; }); } -(void)drawMoney{ dispatch_sync(self.moneyQueue, ^{ [super drawMoney]; }); } -(void)saveMoney{ dispatch_sync(self.moneyQueue, ^{ [super saveMoney]; }); } @end //使用 SerialQueueDemo *demo7 = [[SerialQueueDemo alloc] init]; // [demo7 saleTicketTest]; [demo7 moneyTest];
- 这么也能达到目的
- 比如买票来说,可能多条线程相继访问买票方法saleTicket,但是多条线程的任务放在了一条队列中执行
- 这样就能保证,每个任务都是按顺序执行的,也达到了线程同步的效果
dispatch_semaphore
- semaphore叫做”信号量”
- 信号量的初始值,可以用来控制线程并发访问的最大数量
-
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
//信号量的初始值 int value = 1; //初始化信号量 dispatch_semaphore_t moneySemaphore = dispatch_semaphore_create(1); //如果信号量的值>0,就减1,继续执行代码 //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0) dispatch_semaphore_wait(moneySemaphore, DISPATCH_TIME_FOREVER); //让信号量的值加1 dispatch_semaphore_signal(moneySemaphore);
-
代码示例
#import "SemaphoreDemo.h" @interface SemaphoreDemo () @property (nonatomic, strong) dispatch_semaphore_t moneySemaphore; @property (nonatomic, strong) dispatch_semaphore_t ticketSemaphore; @end @implementation SemaphoreDemo - (instancetype)init { self = [super init]; if (self) { //最大并发信号量是1,信号量的值是1 self.moneySemaphore = dispatch_semaphore_create(1); //最大并发信号量是1,信号量的值是1 self.ticketSemaphore = dispatch_semaphore_create(1); } return self; } -(void)saleTicket{ //如果信号量的值>0,就减1,继续执行代码 //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0) dispatch_semaphore_wait(_ticketSemaphore, DISPATCH_TIME_FOREVER); [super saleTicket]; //让信号量的值加1 dispatch_semaphore_signal(self.ticketSemaphore); } -(void)drawMoney{ //如果信号量的值>0,就减1,继续执行代码 //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0) dispatch_semaphore_wait(_moneySemaphore, DISPATCH_TIME_FOREVER); [super drawMoney]; //让信号量的值加1 dispatch_semaphore_signal(self.moneySemaphore); } -(void)saveMoney{ //如果信号量的值>0,就减1,继续执行代码 //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0) dispatch_semaphore_wait(_moneySemaphore, DISPATCH_TIME_FOREVER); [super saveMoney]; //让信号量的值加1 dispatch_semaphore_signal(self.moneySemaphore); } -(void)otherTest{ for (int i = 0; i<10; i++) { [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start]; } } //由于信号量为1, 那么每一个线程来调用,信号量就会为0了,下一个信号就不能进入了在外面等待,只有当前任务执行完毕,信号量+1,下一个线程才能进来 - (void)test { //如果信号量的值>0,就减1,继续执行代码 //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0) dispatch_semaphore_wait(_moneySemaphore, DISPATCH_TIME_FOREVER); sleep(3); NSLog(@"==test===%@====",[NSThread currentThread]); //让信号量的值加1 dispatch_semaphore_signal(self.moneySemaphore); } @end //使用 SemaphoreDemo *demo8 = [[SemaphoreDemo alloc] init]; // [demo8 otherTest]; // [demo8 moneyTest]; [demo8 saleTicketTest];
@synchronized
- @synchronized是对mutex递归锁的封装
- 源码查看:objc4中的objc-sync.mm文件
-
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
@synchronized (obj) { }
-
举例:
#import "SynDemo.h" @implementation SynDemo -(void)saleTicket{ static NSObject *lock; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ lock = [[NSObject alloc] init]; }); @synchronized (lock) { [super saleTicket]; } } -(void)drawMoney{ @synchronized (self.class) { [super drawMoney]; } } -(void)saveMoney{ @synchronized (self.class) { [super saveMoney]; } } @end
iOS线程同步方案性能比较
-
性能从高到低排序
os_unfair_lock OSSpinLock dispatch_semaphore//常用 pthread_mutex//常用 dispatch_queue(DISPATCH_QUEUE_SERIAL) NSLock NSCondition pthread_mutex(recursive) NSRecursiveLock NSConditionLock @synchronized
-
自旋锁、互斥锁比较
- 什么情况使用自旋锁比较划算?
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
- CPU资源不紧张
- 多核处理器
- 什么情况使用互斥锁比较划算?
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
- 自旋锁相当于while循环线程一直处于忙碌状态,占用CUP资源。互斥锁线程休眠,不占用CUP资源
- 什么情况使用自旋锁比较划算?
atomic
- atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
-
可以参考源码objc4的objc-accessors.mm
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) { bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY); bool mutableCopy = (shouldCopy == MUTABLE_COPY); reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy); } static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) { .... if (!atomic) { oldValue = *slot; *slot = newValue; } else { spinlock_t& slotlock = PropertyLocks[slot]; //加锁 slotlock.lock(); oldValue = *slot; *slot = newValue; //解锁 slotlock.unlock(); } objc_release(oldValue); } //get方法 id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) { if (offset == 0) { return object_getClass(self); } // Retain release world id *slot = (id*) ((char*)self + offset); //不是automic直接返回 if (!atomic) return *slot; //如果atomic // Atomic retain release world spinlock_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); }
-
它并不能保证使用属性的过程是线程安全的
#import <Foundation/Foundation.h> @interface Person : NSObject @property (atomic, assign) int age; @property (atomic,copy) NSString *name; @property (atomic,strong) NSMutableArray *data; @end //使用 Person *person = [[Person alloc] init]; //这两句是线程安全的 //直接调用set方法,set方法有锁,所以是安全的 person.data = [NSMutableArray array]; //直接调用get方法,get方法内部也有锁,所以是安全的 NSMutableArray *arr = person.data; //但是下面不是线程安全的:因为在get方法内部线程是安全的,一旦你通过get方法,拿到这个成员变量,然后设置成员变量,此时设置可以是多线程,就不是安全的了 //atomic只能说是读/写安全的,不能保证成员变量的访问是安全的 [arr addObject:@"1"]; [arr addObject:@"2"]; //person.data安全,addObject;不安全 [person.data addObject:@"3"];
- 既然atomic生成的set、get方法是线程安全的,那为何平时不用呢? 却用nonatomic呢?
- 因为atomic太耗性能,我们平时用的get、set方法太频繁
- 手机内存本身就有限
- iOS开发大部分情况不会出现多条线程同时调用get、set方法
iOS中的读写安全方案
- 思考如何实现以下场景
- 同一时间,只能有1个线程进行写的操作
- 同一时间,允许有多个线程进行读的操作
- 同一时间,不允许既有写的操作,又有读的操作
- 上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有
- pthread_rwlock:读写锁
- dispatch_barrier_async:异步栅栏调用
pthread_rwlock(跨平台)
- 等待锁的线程会进入休眠
-
代码示例:
#import "ViewController.h" #import <pthread.h> @interface ViewController () @property (nonatomic, assign) pthread_rwlock_t rwLock; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //初始化一把锁 pthread_rwlock_init(&_rwLock, NULL); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ dispatch_queue_t queue = dispatch_get_global_queue(0, 0); for (int i = 0; i<5; i++) { dispatch_async(queue, ^{ [[[NSThread alloc] initWithTarget:self selector:@selector(read) object:nil] start]; }); dispatch_async(queue, ^{ [[[NSThread alloc] initWithTarget:self selector:@selector(write) object:nil] start]; }); } } //实现多读 -(void)read{ //读-加锁 pthread_rwlock_rdlock(&_rwLock); //读加锁尝试 // pthread_rwlock_tryrdlock(&_rwLock); sleep(1); NSLog(@"=====%s",__func__); //解锁 pthread_rwlock_unlock(&_rwLock); } //实现一写 -(void)write{ //写-加锁 pthread_rwlock_wrlock(&_rwLock); //写加锁尝试 // pthread_rwlock_trywrlock(&_rwLock); sleep(1); NSLog(@"=====%s",__func__); //解锁 pthread_rwlock_unlock(&_rwLock); } -(void)dealloc{ //销毁 pthread_rwlock_destroy(&_rwLock); } @end
-
打印:
2019-06-14 16:55:11.802254+0800 多线程-01[4062:313431] =====-[ViewController read] 2019-06-14 16:55:12.807465+0800 多线程-01[4062:313432] =====-[ViewController write] 2019-06-14 16:55:13.810705+0800 多线程-01[4062:313434] =====-[ViewController write] 2019-06-14 16:55:14.811285+0800 多线程-01[4062:313437] =====-[ViewController write] 2019-06-14 16:55:15.814354+0800 多线程-01[4062:313438] =====-[ViewController write] 2019-06-14 16:55:16.818855+0800 多线程-01[4062:313439] =====-[ViewController read] 2019-06-14 16:55:16.818855+0800 多线程-01[4062:313433] =====-[ViewController read] 2019-06-14 16:55:17.823437+0800 多线程-01[4062:313440] =====-[ViewController write] 2019-06-14 16:55:18.826876+0800 多线程-01[4062:313441] =====-[ViewController read] 2019-06-14 16:55:18.826876+0800 多线程-01[4062:313436] =====-[ViewController read]
- 从上面的打印时间可以看出,read与read的时间可以是同时进行的(多读)
- write与write的时间一定是不同时进行的(一写)
- 而且read和write之间也一定不是同时进行的(一写)
-
dispatch_barrier_async
- 这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
- 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
-
举例:
#import "ViewController.h" @interface ViewController () @property (nonatomic, strong) dispatch_queue_t queue; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //必须是自己创建的,不能用全局并发队列(global) self.queue = dispatch_queue_create("barrqueue", DISPATCH_QUEUE_CONCURRENT); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ for (int i = 0; i<5; i++) { [self read]; [self read]; [self write]; [self write]; } } - (void)test { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); for (int i = 0; i<5; i++) { dispatch_async(queue, ^{ [[[NSThread alloc] initWithTarget:self selector:@selector(read) object:nil] start]; }); dispatch_async(queue, ^{ [[[NSThread alloc] initWithTarget:self selector:@selector(write) object:nil] start]; }); } } //实现多读 -(void)read{ dispatch_async(self.queue, ^{ sleep(1); NSLog(@"=====%s",__func__); }); } //实现一写 -(void)write{ //执行这个时,会形成一个栅栏,不让别的线程进入执行 dispatch_barrier_sync(self.queue, ^{ sleep(1); NSLog(@"=====%s",__func__); }); }
- 通过打印也能发现,读可以同时发生,写只能单次发生