从前的锁也好看
钥匙精美有样子
你锁了 人家就懂了
一、存在的问题
1、实例
@interface TSDController ()
@property(nonatomic, assign) int totalMoney;
@end
@implementation TSDController
- (void)viewDidLoad {
[super viewDidLoad];
self.totalMoney = 200;
[self earnAndCostMoney];
// Do any additional setup after loading the view.
}
- (void)earnAndCostMoney {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self earnMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self costMoney];
}
});
}
- (void)earnMoney {
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney += 40;
self.totalMoney = oldMoney;
NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
}
- (void)costMoney {
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney -= 20;
self.totalMoney = oldMoney;
NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
}
打印结果(最后余额应该是300的,出问题的结果不一定,此次是320):
花20元,余额180元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
挣40元,余额220元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
花20元,余额200元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
花20元,余额180元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
挣40元,余额240元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
挣40元,余额260元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
花20元,余额220元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
挣40元,余额300元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
花20元,余额280元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
挣40元,余额320元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
2、图解
由上图以及代码可知,在多个线程同时对同一个对象做处理的时候,有可能出现,多个任务在同一时间,没有先后顺序的对同一个对象进行读或写的操作,这样的话,就会出现上面的问题,而解决这一问题的办法就是线程同步方案(线程锁)
二、线程同步方案(线程锁)
1、OSSPinLock: 自旋锁, 性能很高,但是不推荐使用.
导入头文件:#import <libkern/OSAtomic.h>
创建锁:
@property (assign, nonatomic) OSSpinLock lock;
// 初始化锁
self.lock = OS_SPINLOCK_INIT;
加锁:OSSpinLockLock(&_lock);
添加关键代码<><><>
解锁:OSSPinLockUnLock(&_lock)
注意: lock 要做成 全局变量,要使用同一锁才可以。如果有两个方法:判断两个方法 是否能同时执行(读写操作),如果需要这两个方法不能同时执行(读写操作) 则需要共用一把锁。
原理:第二条线程 会等待 (此时会存在线程等待)第一条线程解锁,才会继续执行。
p.s.OSSpinLock 的线程等待会处于忙等状态 (while(1);)会一直占有CPU 的资源 并没有睡觉 休息。 目前此 锁已经不安全了 会出现问题:线程优先级反转问题。ios10 以后使用会警告️。
- (void)earnMoney {
OSSpinLockLock(&_lock);
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney += 40;
self.totalMoney = oldMoney;
NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
OSSpinLockUnlock(&_lock);
}
- (void)costMoney {
OSSpinLockLock(&_lock);
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney -= 20;
self.totalMoney = oldMoney;
NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
OSSpinLockUnlock(&_lock);
}
打印结果:
挣40元,余额240元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
挣40元,余额280元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
挣40元,余额320元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
挣40元,余额360元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
挣40元,余额400元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额380元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额360元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额340元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额320元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额300元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
2. os_unfair_lock 现在它代替 OSSpinLock iOS 10 以后系统使用
导入头文件:import<os/lcok.h>
注意:os_unfair_lock
是一个C语言的结构体,如果要使用属性调用需要使用assign修饰,从iOS10开始支持,用于取代不安全的OSSpinLock
// Low-level lock的特点等不到锁就休眠
@property (assign, nonatomic) os_unfair_lock moneyLock;
使用方法:初始化、加锁、解锁
写法:
- (void)earnMoney {
os_unfair_lock_lock(&_lock);
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney += 40;
self.totalMoney = oldMoney;
NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
os_unfair_lock_unlock(&_lock);
}
- (void)costMoney {
os_unfair_lock_lock(&_lock);
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney -= 20;
self.totalMoney = oldMoney;
NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
os_unfair_lock_unlock(&_lock);
}
打印log和OSSPinLock一样,注意:1、如果忘记解锁,os_unfair_lock的线程会一直处于等待,出现线程死锁现象。2、如果iOS 10 以下版本使用,会出现闪退现象
3、pthread_mutex :多平台通用,跨平台:互斥锁。等待锁的线程或处于休眠状态。区别于 自旋锁(OSSpinLock),自旋锁不会使线程休眠.
使用方式:导入头文件:#import<pthread.h>
静态初始化:pthread_mutex mutex = PTHREAD_MUTEX_INITIALIZER
但是不能直接 赋值给类属性,因为 PTHREAD_MUTEX_INITIALIZER
是一个结构体。
当设置type为 PTHREAD_MUTEX_NORMAL为互斥锁,当设置为PTHREAD_MUTEX_RECURSIVE
时,为递归锁
@property (assign, nonatomic) pthread_mutex_t mutex;
动态初始化方式:
互斥锁
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // normal
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
注意: 这里的属性设置pthread_mutex_init(&ticketMutex, NULL)
; 可以传NULL
,默认就等同于:PTHREAD_MUTEX_DEFAULT
- (void)earnMoney {
pthread_mutex_lock(&_mutex);
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney += 40;
self.totalMoney = oldMoney;
NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
pthread_mutex_unlock(&_mutex);
}
- (void)costMoney {
pthread_mutex_lock(&_mutex);
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney -= 20;
self.totalMoney = oldMoney;
NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
pthread_mutex_unlock(&_mutex);
}
最后注意把mutex 销毁掉
- (void)dealloc
{
pthread_mutex_destroy(& _mutex);
}
递归锁
pthread_mutex的一种,只需要改变 mutex的属性即可: 修改为:PTHREAD_MUTEX_RECURSIVE
注意:
1>如果递归调用的方法里有用到锁,那么就必须用递归锁,否则会出现死锁
2>同一个线程可以对递归锁进行重复上锁
比如:
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // recursive
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self recursive];
});
}
- (void)recursive {
// 将_mutex锁上
pthread_mutex_lock(&_mutex);
static int count = 0;
count++;
if (count <= 5) {
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
// 递归
[self recursive];
}
// 将_mutex打开
pthread_mutex_unlock(&_mutex);
}
// 打印
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
pthread_cond(条件锁)
- 使用
@interface ViewController ()
@property (nonatomic, assign) pthread_cond_t cond;
@property (nonatomic, strong) NSMutableArray *mArray;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化锁
pthread_mutex_init(&_mutex, NULL);
// 初始化条件
pthread_cond_init(&_cond, NULL);
[self condition];
}
- (void)condition {
self.mArray = [NSMutableArray new];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{
sleep(1.0);
[self add];
});
dispatch_async(queue, ^{
[self remove];
});
}
- (void)add {
// 将_mutex锁上
pthread_mutex_lock(&_mutex);
[self.mArray addObject:@"1"];
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
// 发送信号
pthread_cond_signal(&_cond);
// 将_mutex打开
pthread_mutex_unlock(&_mutex);
}
- (void)remove {
// 将_mutex锁上
pthread_mutex_lock(&_mutex);
if (self.mArray.count == 0) {
// 等待信号
pthread_cond_wait(&_cond, &_mutex);
}
[self.mArray removeLastObject];
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
// 将_mutex打开
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc {
// 销毁锁
pthread_mutex_destroy(&_mutex);
// 销毁条件
pthread_cond_destroy(&_cond);
}
// 打印
-[ViewController add]---<NSThread: 0x6000005e3980>{number = 5, name = (null)}
-[ViewController remove]---<NSThread: 0x6000005944c0>{number = 7, name = (null)}
以上如果不加条件信号pthread_cond_signal
和等待信号的话pthread_cond_wait
,那么打印输出的控制台log的次序会正好相反
- 说明
1>某个线程中的操作在某种情况下需要依赖另外一个线程中的操作,那么就可以用条件来实现
2>pthread_cond_wait函数会让线程进入休眠状态,并且将锁打开
3>当收到信号时,pthread_cond_wait函数会唤醒线程,并且将锁再次锁上
4、NSLock
它是对pthread_mutex(normal)
的封装(互斥锁)
使用:
@interface ViewController ()
@property (nonatomic, strong) NSLock *lock;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化锁
self.lock = [NSLock new];
[self earnAndCostMoney];
}
- (void) earnMoney {
// 将lock锁上
[self.lock lock];
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney += 40;
self.totalMoney = oldMoney;
NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
// 将lock打开
[self.lock unlock];
}
- (void) costMoney {
// 将lock锁上
[self.lock lock];
int oldMoney = self.totalMoney;
sleep(0.3);
oldMoney -= 20;
self.totalMoney = oldMoney;
NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
// 将lock打开
[self.lock unlock];
}
5、NSRecursiveLock
它是对pthread_mutex(recursive)
的封装
使用:
@interface ViewController ()
@property (nonatomic, strong) NSRecursiveLock *lock;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化锁
self.lock = [NSRecursiveLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self recursive];
});
}
- (void)recursive {
// 将lock锁上
[self.lock lock];
static int count = 0;
count++;
if (count <= 5) {
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
// 递归
[self recursive];
}
// 将lock打开
[self.lock unlock];
}
6、NSCondition
它是对pthread_mutex
和pthread_cond
的封装
使用:
@interface ViewController ()
@property (nonatomic, strong) NSCondition *lock;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化锁
self.lock = [NSCondition new];
[self condition];
}
- (void)add {
// 将lock锁上
[self.lock lock];
[self.mArray addObject:@"1"];
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
// 发送信号
[self.lock signal];
// 将lock打开
[self.lock unlock];
}
- (void)remove {
// 将lock锁上
[self.lock lock];
if (self.mArray.count == 0) {
// 等待信号
[self.lock wait];
}
[self.mArray removeLastObject];
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
// 将lock打开
[self.lock unlock];
}
7、NSConditionLock
它是对NSCondition
的封装,可以利用条件值来控制线程的执行顺序
@interface ViewController ()
@property (nonatomic, strong) NSConditionLock *lock;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 条件值初始化为1
self.lock = [[NSConditionLock alloc] initWithCondition:1];
[self condition];
}
- (void)condition {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[self one];
});
dispatch_async(queue, ^{
[self two];
});
dispatch_async(queue, ^{
[self three];
});
}
- (void)one {
// 条件值为3往下执行,否则等待
[self.lock lockWhenCondition:3];
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
// 不设置条件值
[self.lock unlock];
NSLog(@"当前条件值---%zd", self.lock.condition);
}
- (void)two {
// 条件值为2往下执行,否则等待
[self.lock lockWhenCondition:2];
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
// 条件值设置为3
[self.lock unlockWithCondition:3];
NSLog(@"当前条件值---%zd", self.lock.condition);
}
- (void)three {
// 条件值为1往下执行,否则等待
[self.lock lockWhenCondition:1];
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
// 条件值设置为2
[self.lock unlockWithCondition:2];
NSLog(@"当前条件值---%zd", self.lock.condition);
}
// 打印
-[ViewController three]---<NSThread: 0x600002ae7400>{number = 4, name = (null)}
当前条件值---2
-[ViewController two]---<NSThread: 0x600002ae2680>{number = 6, name = (null)}
当前条件值---3
-[ViewController one]---<NSThread: 0x600002adfa80>{number = 7, name = (null)}
当前条件值---3