- 先说个概念:
什么是线程安全
?
Thread-safe code only manipulates shared data structures in a manner that ensures that all threads behave properly and fulfil their design specifications without unintended interaction.
简单翻译一下就是:
多线程操作共享数据没有出现意外的结果就是线程安全的,否则,是线程不安全的。
举个:
周杰伦演唱会还剩下20张票,开了2个窗口在卖,用一段代码来演示这个结果:
- (void)sellTicket {
self.ticketCount = 20;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 20; i++) {
if (self.ticketCount > 0) {
self.ticketCount--;
NSLog(@"还剩%ld张票", self.ticketCount);
}
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 20; i++) {
if (self.ticketCount > 0) {
self.ticketCount--;
NSLog(@"还剩%ld张票", self.ticketCount);
}
}
});
}
// 运行结果
2019-06-18 11:48:51.510018+0800 SanJIDemo[46667:17651652] 还剩18张票
2019-06-18 11:48:51.510018+0800 SanJIDemo[46667:17651651] 还剩19张票
2019-06-18 11:48:51.510185+0800 SanJIDemo[46667:17651651] 还剩17张票
2019-06-18 11:48:51.510186+0800 SanJIDemo[46667:17651652] 还剩16张票
2019-06-18 11:48:51.510285+0800 SanJIDemo[46667:17651652] 还剩15张票
2019-06-18 11:48:51.510287+0800 SanJIDemo[46667:17651651] 还剩15张票
2019-06-18 11:48:51.510354+0800 SanJIDemo[46667:17651652] 还剩14张票
2019-06-18 11:48:51.510365+0800 SanJIDemo[46667:17651651] 还剩13张票
2019-06-18 11:48:51.510445+0800 SanJIDemo[46667:17651652] 还剩12张票
2019-06-18 11:48:51.511265+0800 SanJIDemo[46667:17651651] 还剩11张票
2019-06-18 11:48:51.511271+0800 SanJIDemo[46667:17651652] 还剩10张票
2019-06-18 11:48:51.511664+0800 SanJIDemo[46667:17651651] 还剩9张票
2019-06-18 11:48:51.511689+0800 SanJIDemo[46667:17651652] 还剩8张票
2019-06-18 11:48:51.512257+0800 SanJIDemo[46667:17651651] 还剩7张票
2019-06-18 11:48:51.512416+0800 SanJIDemo[46667:17651652] 还剩6张票
2019-06-18 11:48:51.512529+0800 SanJIDemo[46667:17651651] 还剩5张票
2019-06-18 11:48:51.513581+0800 SanJIDemo[46667:17651651] 还剩4张票
2019-06-18 11:48:51.513618+0800 SanJIDemo[46667:17651652] 还剩3张票
2019-06-18 11:48:51.513712+0800 SanJIDemo[46667:17651651] 还剩2张票
2019-06-18 11:48:51.513770+0800 SanJIDemo[46667:17651652] 还剩1张票
2019-06-18 11:48:51.513853+0800 SanJIDemo[46667:17651651] 还剩0张票
票并没有一张的确出现了奇怪的状况,这就是线程不安全导致的结果。
如何避免线程不安全呢?那就单线程
工作呗,绝对万无一失,那开一个窗口卖票?效率未免太低了吧,所以我们需要线程锁
来保证线程安全。
@synchronized
dispatch_async(queue, ^{
for (int i = 0; i < 20; i++) {
@synchronized(self) {
if (self.ticketCount > 0) {
self.ticketCount--;
NSLog(@"还剩%ld张票", self.ticketCount);
}
}
}
});
// 结果
2019-06-18 14:06:01.431450+0800 SanJIDemo[48009:17697855] 还剩19张票
2019-06-18 14:06:01.431664+0800 SanJIDemo[48009:17697855] 还剩18张票
2019-06-18 14:06:01.431749+0800 SanJIDemo[48009:17697855] 还剩17张票
2019-06-18 14:06:01.431868+0800 SanJIDemo[48009:17697855] 还剩16张票
2019-06-18 14:06:01.432415+0800 SanJIDemo[48009:17697855] 还剩15张票
2019-06-18 14:06:01.432505+0800 SanJIDemo[48009:17697855] 还剩14张票
2019-06-18 14:06:01.432608+0800 SanJIDemo[48009:17697854] 还剩13张票
2019-06-18 14:06:01.432692+0800 SanJIDemo[48009:17697854] 还剩12张票
2019-06-18 14:06:01.432899+0800 SanJIDemo[48009:17697854] 还剩11张票
2019-06-18 14:06:01.433098+0800 SanJIDemo[48009:17697854] 还剩10张票
2019-06-18 14:06:01.433366+0800 SanJIDemo[48009:17697854] 还剩9张票
2019-06-18 14:06:01.433587+0800 SanJIDemo[48009:17697854] 还剩8张票
2019-06-18 14:06:01.433812+0800 SanJIDemo[48009:17697854] 还剩7张票
2019-06-18 14:06:01.434039+0800 SanJIDemo[48009:17697855] 还剩6张票
2019-06-18 14:06:01.434279+0800 SanJIDemo[48009:17697854] 还剩5张票
2019-06-18 14:06:01.434692+0800 SanJIDemo[48009:17697854] 还剩4张票
2019-06-18 14:06:01.434777+0800 SanJIDemo[48009:17697854] 还剩3张票
2019-06-18 14:06:01.435015+0800 SanJIDemo[48009:17697854] 还剩2张票
2019-06-18 14:06:01.436214+0800 SanJIDemo[48009:17697854] 还剩1张票
2019-06-18 14:06:01.436300+0800 SanJIDemo[48009:17697854] 还剩0张票
结果显示正常,票一张一张的卖完了,这就是线程安全的结果。
-
NSLock
内部封装了一个pthread_mutex
,属性为PTHREAD_MUTEX_ERRORCHECK
。
dispatch_async(queue, ^{
for (int i = 0; i < 20; i++) {
[self.lock lock];
if (self.ticketCount > 0) {
self.ticketCount--;
NSLog(@"还剩%ld张票", self.ticketCount);
}
[self.lock unlock];
}
});
// 运行结果同上
- 用
dispatch_barrier_async
设置同步锁,把self.ticketCount
的读写操作放在一个并发队列里,同时setter
设置栅栏函数异步,getter
同步读取,保证self.ticketCount
的线程安全。
- (void)setTicketCount:(NSInteger)ticketCount {
dispatch_barrier_async(self.queue, ^{
_ticketCount = ticketCount;
});
}
- (NSInteger)ticketCount {
__block NSInteger temp;
dispatch_sync(self.queue, ^{
temp = _ticketCount;
});
return temp;
}
- (dispatch_queue_t)queue {
if (!_queue) {
_queue = dispatch_queue_create("wwww", DISPATCH_QUEUE_CONCURRENT);
}
return _queue;
}
这里有个注意的点:barrier
只支持dispatch_queue_create
创建出来的并发队列,queue.h
文档里说了:
When submitted to a a global queue or to a queue not created with the
* DISPATCH_QUEUE_CONCURRENT attribute, barrier blocks behave identically to
* blocks submitted with the dispatch_async()/dispatch_sync() API.
翻译下:
当栅栏函数往一个全局并发队列或者不是由`DISPATCH_QUEUE_CONCURRENT `创建出来的,
栅栏函数表现得会跟`dispatch_async()/dispatch_sync()`一样,起不到栅栏的作用。
-
dispatch_semaphore
有三个方法:
dispatch_semaphore_create
创建信号
dispatch_semaphore_wait
信号等待
dispatch_semaphore_signal
放开信号
字面理解就是信号量
的意思,可以理解为高速路口的栏杆,如果dispatch_semaphore_create(N)
,如果N = 1
,每个信号只能放行一个车,等待下一个信号放出才能放行下一辆车,所以创建信号量为1的信号可以做到线程安全。
- (void)sellTicket {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
self.ticketCount = 20;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0 );
dispatch_async(queue, ^{
for (int i = 0; i < 20; i++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) {
self.ticketCount--;
NSLog(@"还剩%ld张票", self.ticketCount);
}
dispatch_semaphore_signal(semaphore);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 20; i++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) {
self.ticketCount--;
NSLog(@"还剩%ld张票", self.ticketCount);
}
dispatch_semaphore_signal(semaphore);
}
});
}
-
NSCondition
条件锁
// 让当前线程处于等待状态
- (void)wait;
// 让当前线程等待到某个日期
- (BOOL)waitUntilDate:(NSDate *)limit;
// 释放信号
- (void)signal;
// 广播 ??
- (void)broadcast;
同时,NSCondition
还遵循了一个protocol
,这就是加锁与解锁的方法了,使用方法跟NSLock
一样。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
-
NSConditionLock
对NSCondition
的进一步封装
Apple官方文档的注释:
A lock that can be associated with specific, user-defined conditions.
-
OSSpinLock
自旋锁,不再线程安全,具体参考不再安全的 OSSpinLock
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnlock(&lock);