iOS 多线程安全问题以及各种锁使用
2019-03-06 11:53
260 查看
多线程安全问题
多线程使用不当会出现资源竞争,比如多个线程同时对一块资源进行修改,就会出现数据错乱的情况。
@property (nonatomic, assign) int ticketsCount; - (void)ticketTest { self.ticketsCount = 15; dispatch_queue_t quene = dispatch_get_global_queue(0, 0); dispatch_async(quene, ^{ for (int i = 0; i < 5; i++) { [self saleticket]; } }); dispatch_async(quene, ^{ for (int i = 0; i < 5; i++) { [self saleticket]; } }); dispatch_async(quene, ^{ for (int i = 0; i < 5; i++) { [self saleticket]; } }); } - (void)saleticket { int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]); }
调用
ticketTest方法
打印结果:
还剩14张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)} 还剩14张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)} 还剩14张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)} 还剩13张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)} 还剩13张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)} 还剩12张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)} 还剩11张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)} 还剩10张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)} 还剩9张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)} 还剩9张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)} 还剩8张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)} 还剩6张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)} 还剩7张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)} 还剩5张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)} 还剩4张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)}
总票数为15,虽然子线程调用
saleticket方法15次,但是打印的数据并没有递减为0。
线程同步技术
常见的线程同步技术是加锁
-
OSSpinLock
自旋锁
导入头文件#import <libkern/OSAtomic.h>
- (void)saleticket { // 初始化 static OSSpinLock lock = OS_SPINLOCK_INIT; // 加锁 OSSpinLockLock(&lock); int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]); // 解锁 OSSpinLockUnlock(&lock); }
调用
ticketTest
方法
打印结果:还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
自旋锁等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源。
并且现在已经不安全,可能出现优先级反转的问题。
如果等待锁的优先级较高,它会一直占用着CPU的资源,优先级低的线程就无法释放锁。
在iOS10被苹果废弃。 -
os_unfair_lock
导入头文件#import <os/lock.h>
@property (assign, nonatomic) os_unfair_lock lock; - (instancetype)init { if (self = [super init]) { // 初始化 self.lock = OS_UNFAIR_LOCK_INIT; } return self; } - (void)saleticket { // 加锁 os_unfair_lock_lock(&lock); int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]); // 解锁 os_unfair_lock_unlock(&lock); }
调用
ticketTest
方法
打印结果:还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
os_unfair_lock
用于取代不安全的OSSpinLock
。
等待os_unfair_lock
锁的线程处于休眠状态,不是忙等状态。
在iOS10开始使用。 -
pthread_mutex
互斥锁普通用法
导入头文件
#import <pthread.h>
@property (assign, nonatomic) pthread_mutex_t mutex; - (void)__initMutex:(pthread_mutex_t *)mutex { // // 初始化属性 // pthread_mutexattr_t attr; // pthread_mutexattr_init(&attr); // pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); // // 初始化锁 // pthread_mutex_init(mutex, &attr); // // 销毁属性 // pthread_mutexattr_destroy(&attr); // 初始化锁 NULL等同于PTHREAD_MUTEX_DEFAULT pthread_mutex_init(mutex, NULL); } - (instancetype)init { if (self = [super init]) { // 初始化 [self __initMutex:&_mutex]; } return self; } - (void)saleticket { // 加锁 pthread_mutex_lock(&_mutex); int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]); // 解锁 pthread_mutex_unlock(&_mutex); }
调用
ticketTest
方法
打印结果:还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
mutex
也叫“互斥锁”,等待锁的线程处于休眠状态,不是忙等状态。/* * 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
上面使用的是Normal类型同
OSSpinLock
和os_unfair_lock
作用一样递归用法
递归锁在被同一线程重复获取时不会产生死锁
递归类型PTHREAD_MUTEX_RECURSIVE
:- (void)__initMutex:(pthread_mutex_t *)mutex { // 递归锁:允许同一个线程对一把锁进行重复加锁 // 初始化属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 初始化锁 pthread_mutex_init(mutex, &attr); // 销毁属性 pthread_mutexattr_destroy(&attr); } - (instancetype)init { if (self = [super init]) { [self __initMutex:&_mutex]; } return self; } /** 线程1:otherTest(+-) otherTest(+-) otherTest(+-) 线程2:otherTest(等待) */ - (void)otherTest { pthread_mutex_lock(&_mutex); NSLog(@"%s", __func__); static int count = 0; if (count < 10) { count++; [self otherTest]; } pthread_mutex_unlock(&_mutex); } - (void)dealloc { pthread_mutex_destroy(&_mutex); }
条件用法
@property (assign, nonatomic) pthread_mutex_t mutex; @property (assign, nonatomic) pthread_cond_t cond; @property (strong, nonatomic) NSMutableArray *data; - (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]; } return self; } - (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_cond_broadcast(&_cond); pthread_mutex_unlock(&_mutex); } - (void)dealloc { pthread_mutex_destroy(&_mutex); pthread_cond_destroy(&_cond); }
-
NSLock、NSRecursiveLock
NSLock
是对mutex
普通锁的封装
NSRecursiveLock
是对mutex
递归锁的封装@property (strong, nonatomic) NSLock *lock; - (instancetype)init { if (self = [super init]) { // 初始化 self.lock = [[NSLock alloc] init]; } return self; } - (void)saleticket { // 加锁 [self.lock lock]; int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]); // 解锁 [self.lock unlock]; }
调用
ticketTest
方法
打印结果:还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
NSRecursiveLock
用法与NSLock
的用法一样。 -
NSCondition
NSCondition
是对mutex和cond的封装@property (strong, nonatomic) NSCondition *condition; @property (strong, nonatomic) NSMutableArray *data; - (instancetype)init { if (self = [super init]) { self.condition = [[NSCondition alloc] init]; self.data = [NSMutableArray array]; } return self; } - (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 { [self.condition lock]; NSLog(@"__remove - begin"); 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 broadcast]; [self.condition unlock]; }
-
NSConditionLock
它是对NSCondition
的进一步封装,可以设置具体的条件值@property (strong, nonatomic) NSConditionLock *conditionLock; - (instancetype)init { if (self = [super init]) { // 初始化的时候 设置条件为1 self.conditionLock = [[NSConditionLock alloc] initWithCondition:1]; } return self; } - (void)otherTest { [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start]; } - (void)__one { // 加锁 [self.conditionLock lock]; NSLog(@"__one"); sleep(1); // 解锁时设置锁的条件为2 [self.conditionLock unlockWithCondition:2]; } - (void)__two { // 当条件为2时 加锁 [self.conditionLock lockWhenCondition:2]; NSLog(@"__two"); sleep(1); // 解锁时设置锁的条件为3 [self.conditionLock unlockWithCondition:3]; } - (void)__three { // 当条件为3时 加锁 [self.conditionLock lockWhenCondition:3]; NSLog(@"__three"); // 解锁 [self.conditionLock unlock]; }
-
dispatch_queue
直接使用GCD的串行队列,也可以实现线程同步@property (strong, nonatomic) dispatch_queue_t queue; - (instancetype)init { if (self = [super init]) { // 初始化 self.queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL); } return self; } - (void)saleticket { dispatch_sync(self.moneyQueue, ^{ int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]); }); }
调用
ticketTest
方法
打印结果:还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)} 还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)} 还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)} 还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
-
dispatch_semaphore
信号量的初始值可以控制线程并发访问的最大值
初始值为1时,代表同时只允许1条线程访问资源,保证线程的同步@property (strong, nonatomic) dispatch_semaphore_t semaphore; @property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore; - (instancetype)init { if (self = [super init]) { // 初始化 self.semaphore = dispatch_semaphore_create(5); self.ticketSemaphore = dispatch_semaphore_create(1); } return self; } - (void)saleticket { // 让信号量的值-1 为0 则等待,相当于加锁 dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER); int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]); // 让信号量的值+1,相当于解锁 dispatch_semaphore_signal(self.ticketSemaphore); } - (void)otherTest { for (int i = 0; i < 20; i++) { [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start]; } } // 线程的最大并发数为 5 - (void)test { // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码 // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码 dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); sleep(2); NSLog(@"test - %@", [NSThread currentThread]); // 让信号量的值+1 dispatch_semaphore_signal(self.semaphore); }
调用otherTest会发现每隔两秒打印5条信息。
-
@synchronized
是对mutex的递归锁的封装,源码objc4
中的objc-sync.mm
文件查看
@synchronized(obj)
内部会生成obj
对应的递归锁,然后进行加锁解锁操作- (void)saleticket { @synchronized(self) { // objc_sync_enter int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]); } // objc_sync_exit }
相关文章推荐
- iOS多线程全套:线程安全问题,GCD的使用,NSOperation的使用
- IOS学习日志---1:TableView下拉刷新以及各种失败问题总结
- PHP_thinkphp框架使用PHPMailer实现发送邮件的功能,以及各种问题解析
- 黑马程序员_多线程之同步问题的前期,以及安全问题的发现和解决
- iOS多线程安全的几种解决方案以及性能对比
- IOS开发中遇到的各种问题以及解决方案集锦
- iOS开发小记:关于环信Demo3.0的使用总结以及昵称和头像问题的研究与解决
- CoreData的使用/以及coreData中的多线程问题/版本迁移(二)
- iOS多线程——Dispatch Source 工程广告页有使用以及Iphone X 广告页启动变形
- 线程是什么?线程和进程的区别,怎么最好的理解使用和快熟掌握多线程?多线程的各种坑以及多线程锁的简介和使用
- iOS开发工具-如何使用网络封包分析工具Charles,通过配置proxy对http、https、tcp、udp 等协议的请求响应过程交互信息进行分析、判断、解决我们移动开发中的遇到的各种实际问题。
- IOS开发中遇到的各种问题以及解决方案集锦
- iOS边练边学--多线程练习的多图片下载 以及 使用第三方框架(SDWebImage)的多图片下载
- iOS开发--iOS多线程操作时一些要注意的安全问题
- iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用(上)
- iOS cocoPods使用流程以及常见问题
- iOS开发之WKWebView的使用以及遇到的问题
- 使用Threadlocal来解决SimpleDateFormat的多线程安全问题
- OGRE库编译和使用问题,重定义以及dxguid.lib 调试信息损坏,请重新编译 等各种问题解决
- iOS 多线程下安全的使用定时器