多线程的安全隐患
一块资源可能会被多个线程共享,也就是说多个线程可能会访问同一块资源。
比如多个线程同时操作同一个对象,同一个变量。
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。
比如一个买票问题:
#import "ViewController.h"
@interface ViewController ()
@property (assign, nonatomic) NSInteger maxCount;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_maxCount = 20;
// 线程1
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 线程2
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
- (void)run{
while (1) {
if (_maxCount > 0) {
// 暂停一段时间
[NSThread sleepForTimeInterval:0.002];
_maxCount--;
NSLog(@"卖了一张票 - %ld - %@",_maxCount,[NSThread currentThread]);
}else{
NSLog(@"票卖完了");
break;
}
}
}
输出结果:

可以看到,当多个线程同时访问同一个数据的时候,很容易出现数据错乱,资源争夺的现象。
1. @synchronized(锁对象) { // 需要锁定的代码 } 来解决
互斥锁,使用的是线程同步的技术。加锁的代码需要尽量少,这个锁 ?? 对象需要保持在多个线程中都是同一个对象。
优点:是不需要显示的创建锁对象,就可以实现锁的机制。
缺点:会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁,会消耗系统资源。
- (void)run{
while (1) {
// 这里使用self,或者一个全局对象也行,每个对象里面都有一把锁
@synchronized(self){
if (_maxCount > 0) {
// 暂停一段时间
[NSThread sleepForTimeInterval:0.002];
_maxCount--;
NSLog(@"卖了一张票 - %ld - %@",_maxCount,[NSThread currentThread]);
}else{
NSLog(@"票卖完了");
break;
}
}
}
}
输出结果:

2. NSLock
在Cocoa框架中,NSLock实现了一个简单的互斥锁,所有锁(包括NSLock)的接口,实际上都是通过NSLocking协议定义的,它定义了 lock(加锁)和 unlock(解锁)方法。
不能多次调用lock方法,会造成死锁。
我们使用NSLock来解决上面的问题:
#import "ViewController.h"
@interface ViewController ()
@property (assign, nonatomic) NSInteger maxCount;
@property (strong, nonatomic) NSLock *lock; // 数据所
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_maxCount = 20;
_lock = [[NSLock alloc] init];
// 线程1
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 线程2
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
- (void)run{
while (1) {
// 加锁
[_lock lock];
if (_maxCount > 0) {
// 暂停一段时间
[NSThread sleepForTimeInterval:0.002];
_maxCount--;
NSLog(@"卖了一张票 - %ld - %@",_maxCount,[NSThread currentThread]);
}else{
NSLog(@"票卖完了");
break;
}
// 释放锁
[_lock unlock];
}
}
输出结果:

3. NSRecursiveLock 递归锁
如果在循环中,使用锁,很容易造成死锁。如下代码,在递归block中,多次的调用lock方法,锁会被多次的lock,所以自己也被阻塞了。
_lock = [[NSLock alloc] init];
//线程1
dispatch_async(dispatch_queue_create(NULL, 0), ^{
static void(^TestMethod)(int);
TestMethod = ^(int value)
{
[_lock lock];
if (value > 0)
{
[NSThread sleepForTimeInterval:1];
NSLog(@"执行一次哦");
TestMethod(value--);
}
NSLog(@"是否执行到这里");
[_lock unlock];
};
TestMethod(5);
});
输出内容:
2017-12-12 11:39:50.253155+0800 NSLock[1353:158620] 执行一次哦
此处将NSLock 换成 NSRecursiveLock 便可解决问题:
NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它会被多少次lock,每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其它线程获得。
_lock = [[NSRecursiveLock alloc] init];
//线程1
dispatch_async(dispatch_queue_create(NULL, 0), ^{
static void(^TestMethod)(int);
TestMethod = ^(int value)
{
[_lock lock];
if (value > 0)
{
[NSThread sleepForTimeInterval:1];
NSLog(@"执行了一次哦");
TestMethod(value--);
}
NSLog(@"是否执行到这里");
[_lock unlock];
};
TestMethod(5);
});
执行结果:
2017-12-12 11:49:43.378299+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:44.380543+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:45.382145+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:46.387148+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:47.388813+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:48.389408+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:49.392983+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:50.396521+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:51.399108+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:52.399976+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:53.404280+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:54.409044+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:55.412670+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:56.413754+0800 NSLock[1419:176157] 执行了一次哦 2017-12-12 11:49:57.414257+0800 NSLock[1419:176157] 执行了一次哦
4. NSConditionLock 条件锁
NSConditionLock相比NSLock多了一个condition参数,我们可以理解为一个条件标示。
@property (readonly)NSInteger condition; //这属性非常重要,外部传入的condition与之相同才会获取到lock对象,反之阻塞当前线程,直到condition相同 - (void)lockWhenCondition:(NSInteger)condition; //condition与内部相同才会获取锁对象并立即返回,否则阻塞线程直到condition相同 - (BOOL)tryLock;//尝试获取锁对象,获取成功需要配对unlock - (BOOL)tryLockWhenCondition:(NSInteger)condition; //同上 - (void)unlockWithCondition:(NSInteger)condition; //解锁,并且设置lock.condition = condition
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if([cLock tryLockWhenCondition:0]){
NSLog(@"线程1");
[cLock unlockWithCondition:1];
}else{
NSLog(@"失败");
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:3];
NSLog(@"线程2");
[cLock unlockWithCondition:2];
});
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:1];
NSLog(@"线程3");
[cLock unlockWithCondition:3];
});
输出结果:
2017-12-12 13:32:24.060013+0800 条件锁 - NSConditionLock[1783:250080] 线程1 2017-12-12 13:32:24.060461+0800 条件锁 - NSConditionLock[1783:250078] 线程3 2017-12-12 13:32:24.060626+0800 条件锁 - NSConditionLock[1783:250079] 线程2
- 我们在初始化这个lock的时候,给定了它的初始条件为0;
- 执行tryLockWhenCondition:时,我们传入的条件标示也是0,所以线程1加锁成功。
- 执行unLockWithCondition:时,这时候会把condition将0改为1
- 因为condition为1,所以先走线程3,然后线程3将condition修改为3,
- 最后走了线程2
可以看出,NSConditionLock还可以实现任务之间的依赖。
5. NSCondition
NSCondition实际上作为一个锁和一个线程检查器。锁主要是为了检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续进行下去,即线程是否被阻塞。
NSCondition实现了NSLocking协议,当多个线程访问同一段代码,会以wait为分水岭,一个线程等待另一个线程unlock后,才会走wait之后的代码。
[condition lock];//一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问 [condition unlock];//与lock 同时使用 [condition wait];//让当前线程处于等待状态 [condition signal];//CPU发信号告诉线程不用在等待,可以继续执行
我们可以来看一个生产者消费者的问题,
消费者取得锁,取产品,如果没有产品,则wait等待,这时候会释放锁,知道有线程去唤醒它去消费产品。
生产者制造产品,首先也要取得锁,然后生产,再发signal,这样可以唤醒wait的消费者
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) NSCondition *condition;
@property (assign ,nonatomic) NSInteger goodNum;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_goodNum = 0;
_condition = [[NSCondition alloc] init];
[NSThread detachNewThreadSelector:@selector(buyGoods) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(shopGoods) toTarget:self withObject:nil];
}
- (void)buyGoods{
[_condition lock];
while (_goodNum == 0){
NSLog(@"当前售卖个数为0,不能卖,等待");
[_condition wait];
}
_goodNum--;
NSLog(@"买了一个 - %ld",_goodNum);
[_condition unlock];
}
- (void)shopGoods{
[_condition lock];
_goodNum++;
NSLog(@"准备卖一个 - %ld",_goodNum);
[_condition signal];
NSLog(@"告诉买家可以买了");
[_condition unlock];
}
输出结果:
2017-12-12 14:43:48.995787+0800 NSCondition[2410:357340] 当前售卖个数为0,不能卖,等待 2017-12-12 14:43:48.996067+0800 NSCondition[2410:357341] 准备卖一个 - 1 2017-12-12 14:43:48.996578+0800 NSCondition[2410:357341] 告诉买家可以买了 2017-12-12 14:43:48.997806+0800 NSCondition[2410:357340] 买了一个 - 0
我们可以看出,当消费者想要买东西的时候,因为商品初始数量为0,所以只能等待生产者去生产商品,准备售卖,有商品之后,会signal,告诉消费者可以买了。
6. 使用C语言的pthread_mutex_t实现的锁
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
NSLog(@"线程1开始执行");
for (NSInteger i = 1; i <= 10; i++) {
sleep(1);
NSLog(@"线程1 在执行 ---- %ld",i);
}
pthread_mutex_unlock(&mutex);
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
pthread_mutex_lock(&mutex);
NSLog(@"线程1执行完了,线程2开始执行");
pthread_mutex_unlock(&mutex);
});
}
输出结果:
2017-12-12 14:54:16.343540+0800 pthread_mutex - 互斥锁[2518:371518] 线程1开始执行 2017-12-12 14:54:17.348019+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 1 2017-12-12 14:54:18.348439+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 2 2017-12-12 14:54:19.351159+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 3 2017-12-12 14:54:20.355283+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 4 2017-12-12 14:54:21.358290+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 5 2017-12-12 14:54:22.361572+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 6 2017-12-12 14:54:23.362013+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 7 2017-12-12 14:54:24.363130+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 8 2017-12-12 14:54:25.366042+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 9 2017-12-12 14:54:26.370250+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 10 2017-12-12 14:54:26.370496+0800 pthread_mutex - 互斥锁[2518:371519] 线程1执行完了,线程2开始执行