线程安全隐患解决方案
解决方案 使用线程同步技术(按照预定的先后次序进行)
#import "<libkern/OSAtomic.h>"
OSSpinLock _lock = OS_SPINLOCK_INIT; OSSpinLockLock(&_lock); OSSpinLockUnlock(&_lock); OSSpinLockTry(&_lock); //尝试加锁 如果不能加锁 返回No 复制代码
由于锁是自旋锁,线程不会休眠,所以当低优先级线程先对操作进行Lock造作后,CPU调度高优先级线程造作,由于低优先级别UnLock就调用高优先级线程。高优先级无法处理该操作,而高优先级线程一直调用CPU资源, 系统等待高优先级线程执行完毕后才给低优先级线程资源。
高优先级线程等待低优先级线程Unlock。低优先级线程等待CPU资源调度。造成类似死锁 导致锁无法释放。相互等待
import <os.lock.h>
os_unfair_lock _lock = OS_UNFAIR_LOCK_INIT; os_unfair_lock_lock(&_lock); os_unfair_lock_unlock(&_lock); os_unfair_lock_trylock(&_lock);//尝试加锁 如果不能加锁 返回No 复制代码
#import"pthread.h"
pthread_mutexattr_t attr = {0}; pthread_mutexattr_init(&attr); //设置锁的描述 PTHREAD_MUTEX_DEFAULT为一般互斥锁 PTHREAD_MUTEX_RECURSIVE为递归锁 //pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE为递归锁); //递归锁 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(mutex, &attr); //释放 防止内存泄漏 pthread_mutexattr_destroy(&attr); 复制代码
根据参数 attr PTHREAD_MUTEX_RECURSIVE 递归锁
PTHREAD_MUTEX_RECURSIVE :允许同一个线程 重复加锁。
线程激活信号 不一定会马上执行 先等上一次操作unlock
// 创建锁 - (void) __initLock:(pthread_mutex_t *) mutex { pthread_mutexattr_t attr = {0}; pthread_mutexattr_init(&attr); //互斥锁 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(mutex, &attr); pthread_mutexattr_destroy(&attr); pthread_condattr_t condAtt={0}; // 创建条件 pthread_cond_init(&_condition, &condAtt); pthread_condattr_destroy(&condAtt); } - (instancetype)init { self = [super init]; if (self) { [self __initLock:&_lock]; dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ [self test2]; }); dispatch_async(queue, ^{ sleep(2); [self test1]; }); } return self; } - (void)test1 { NSLog(@"%@",[NSThread currentThread]); // lock pthread_mutex_lock(&_lock); NSLog(@"1"); //对condition发送信号 pthread_cond_signal( &_condition); NSLog(@"3"); //unlock pthread_mutex_unlock(&_lock); NSLog(@"4"); } - (void)test2 { NSLog(@"%@",[NSThread currentThread]); // lock pthread_mutex_lock(&_lock); // 等待condition发送信号 pthread_cond_wait(&_condition, &_lock); NSLog(@"2"); //unlock pthread_mutex_unlock(&_lock); } 复制代码
输出结果为
2019-09-03 14:55:49.650729+0800 GCD[1918:36391522] 1 2019-09-03 14:55:49.650896+0800 GCD[1918:36391522] 3 2019-09-03 14:55:49.651025+0800 GCD[1918:36391522] 4 2019-09-03 14:55:49.651031+0800 GCD[1918:36391523] 2 复制代码
上述代码 无论test1 和test2谁先调用 test2 依赖于test1的调用 只有当test1中执行 pthread_cond_signal( &_condition);
test2中才会继续执行, 执行步骤为:
pthread_mutex_lock(&_lock);
pthread_cond_wait(&_condition, &_lock);
此时unlock锁 并且线程休眠pthread_mutex_lock(&_lock);
pthread_cond_signal( &_condition);
test2线程被激活pthread_cond_boradcast
激活所有pthread_cond_waitNSLock是对mutex PTHREAD_MUTEX_DEFAULT
普通锁的封装
- (void)lock;// 加锁 - (void)unlock;//解锁 - (BOOL)tryLock;// 尝试是否可以加锁 如果可以 直接加锁返回YES - (BOOL)lockBeforeDate:(NSDate *)limit; //在一定时间内是否可以加锁 如果可以 直接加锁返回YES 复制代码
NSLock是对mutex PTHREAD_MUTEX_RECURSIVE
递归锁的封装
API和NSLock一样
- (void)lock;// 加锁 - (void)unlock;//解锁 - (BOOL)tryLock;// 尝试是否可以加锁 如果可以 直接加锁返回YES - (BOOL)lockBeforeDate:(NSDate *)limit; //在一定时间内是否可以加锁 如果可以 直接加锁返回YES 复制代码
NSCondition是对mutex和cond的封装
//用法和mutex condtion 一样 可以参考上面代码 - (void)lock;// 加锁 - (void)unlock;//解锁 - (void)wait;//等待 - (BOOL)waitUntilDate:(NSDate *)limit; //等待多久 如果超时就不等了 就直接执行 - (void)signal; // 信号量 - (void)broadcast; //广播 复制代码
NSConditionLock是对NSCondition的封装
- (void)lock;// 加锁 - (void)unlock;//解锁 - (void)lockWhenCondition:(NSInteger)condition; //休眠 直到符合conditon后 激活并lock - (BOOL)tryLock; - (BOOL)tryLockWhenCondition:(NSInteger)condition; - (void)unlockWithCondition:(NSInteger)condition; // 解锁 修改condtion的值 - (BOOL)lockBeforeDate:(NSDate *)limit; - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit; 复制代码
Dispatch_semaphore 可以控制线程的个数 当控制线程个数为1的时候 能确保同时只有1条线程去访问,已达到确保线程安全的目的
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); // 如果信号量<=0 当前线程就会键入休眠状态 知道信号量的值>0 // 如果信号值>0 就-1 然后执行下面代码 dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); [super __saleTicket]; // 信号量+1 dispatch_semaphore_signal(_semaphore); 复制代码
串行队列,当在一个串行队列执行的时候,只有一个线程,能确保线程安全
以前:对mutex递归锁的封装
现在:os_unfair_recursive_lock的封装
首先打开断点汇编模式
obj4源码中objc_sync.mm
底层存储 hash表 key :传进去的对象
class recursive_mutex_tt : nocopy_t { os_unfair_recursive_lock mLock; ... } using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>; 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; 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 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 { // @synchronized(nil) does nothing } return result; } 复制代码
至于耗时,测的时候每次都不太一样,大家自己测一下吧~
本文分享自微信公众号 - 老沙课堂(gh_f73a6b772d4f),作者:rui4u
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2019-09-03
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句