一、iOS 中的线程同步方案 -> 加锁
二、OSSpinLock
三、 示例程序: 卖票
没加锁代码:
/// 卖1张票
- (void)saleTicket {
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d 张票 -%@", oldTicketsCount, [NSThread currentThread]);
}
/// 卖票演示
- (void)ticketTest {
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0 ; i < 3; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 3; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 3; i++) {
[self saleTicket];
}
});
}
加锁代码:
加锁: 别的线程无法再进行访问
解锁: 用完后,解除锁定,让其他线程可以访问。
如果使用须导入 #import
第一次加锁:在 saleTicket 方法中 ,一开始就加锁
/// 卖1张票
- (void)saleTicket {
// 加锁 - 初始化 - iOS10 已经弃用
OSSpinLock look = OS_SPINLOCK_INIT;
// 加锁, 因为要传的是指针,所以传入地址
OSSpinLockLock(&look);
// 原来代码
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d 张票 -%@", oldTicketsCount, [NSThread currentThread]);
// 解锁
OSSpinLockUnlock(&look);
}
OSSpinLock look = OS_SPINLOCK_INIT;
是局部变量,每次进入到 saleTicket
这个方法的时候,都会重新创建。@property(nonatomic,assign) OSSpinLock look;
- (void)viewDidLoad {
[super viewDidLoad];
// 加锁 - 初始化 - iOS10 已经弃用
_look = OS_SPINLOCK_INIT;
[self ticketTest];
}
/// 卖1张票
- (void)saleTicket {
// 加锁, 因为要传的是指针,所以传入地址
OSSpinLockLock(&_look);
// 原来代码
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d 张票 -%@", oldTicketsCount, [NSThread currentThread]);
// 解锁
OSSpinLockUnlock(&_look);
}
四、示例程序:存钱取钱
*没有加锁的 写法
- (void)viewDidLoad {
[super viewDidLoad];
// 加锁 - 初始化 - iOS10 已经弃用
_look = OS_SPINLOCK_INIT;
[self moneyTest];
}
/// 存钱,取钱演示
- (void)moneyTest {
self.money = 50;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0 ; i < 5; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 5; i++) {
[self drawMoney];
}
});
}
/// 取钱
- (void)drawMoney {
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取 20 , 还剩 %d 元 - %@",oldMoney, [NSThread currentThread]);
}
/// 存钱
- (void)saveMoney {
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存 50 , 还剩 %d 元 - %@",oldMoney, [NSThread currentThread]);
}
思考:
* 存钱取钱 是否能同时执行?
* 不能
* 这两个操作在同一时间段只能执行一个 。
* 所以可以使用一把锁
* 如果 锁不一样,意味着 同一时间段可以两个一起执行。
* 只有大家共用一把锁,才能保证同一时间段只能执行一个操作。
加锁之后的 代码
- (void)viewDidLoad {
[super viewDidLoad];
// 加锁 - 初始化 - iOS10 已经弃用
_look = OS_SPINLOCK_INIT;
[self moneyTest];
}
/// 存钱,取钱演示
- (void)moneyTest {
self.money = 50;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0 ; i < 5; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 5; i++) {
[self drawMoney];
}
});
}
/// 取钱
- (void)drawMoney {
OSSpinLockLock(&_look);
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取 20 , 还剩 %d 元 - %@",oldMoney, [NSThread currentThread]);
// 解锁
OSSpinLockUnlock(&_look);
}
/// 存钱
- (void)saveMoney {
OSSpinLockLock(&_look);
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存 50 , 还剩 %d 元 - %@",oldMoney, [NSThread currentThread]);
// 解锁
OSSpinLockUnlock(&_look);
}
五、存钱取钱 和 卖票
如果存钱取钱 和 卖票 使用同一把锁,会出现什么事情?
六、OSSpinLock 解释
加锁其实就是 线程阻塞
常见的线程阻塞有两种
OSSpinLock 这个锁的线程阻塞就是 忙等。
while(锁还没被放开);
目前 OSSpinLock 已经不在安全,可能会出现优先级翻转问题。
例如: 我们有三个线程 , thread1,thread2,thread3。当我们支持多线程,并开启三条线程时,有可能3条线程同时做事情。
系统是如何调度三条线程的?
使用自旋锁 会发生什么事情?
OSSpinLockLock(&_lock)
加锁。这样当线程1 进入时,发现所已经被加了。线程1 只能忙等。也就是在做 while(未解锁)
这一行代码。while(未解锁)
这一行代码。这就是 自旋锁会发生的事情。
如果是 睡眠,就不会发生这种事情。
线程就不分配时间给thread1, thread2 就可以继续往下走,就可以完成解锁操作。然后 thread1 从休眠中唤醒,发现锁已经解开,thread1 就进行加锁,往下执行。
这就是为啥在 使用 OSSpinLockLock 的时候,苹果会提示 这个锁已经 不在使用。
七、另一种加锁方式
OSSpinLockTry : 尝试加锁
* 如果内容没有被加锁,就尝试加锁
* 如果被加锁,就不执行大括号中的代码。
/// 取钱
- (void)drawMoney {
// 尝试加锁,返回BOOL 值
bool result = OSSpinLockTry(&_look);
// 如果没有被加锁,就尝试加锁。
// 如果被加锁,就不执行 大扩号中的代码,不会阻塞线程,代码继续往下走
if (result) {
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取 20 , 还剩 %d 元 - %@",oldMoney, [NSThread currentThread]);
// 解锁
OSSpinLockUnlock(&_look);
}
}
八、锁的多样性创建
- (void)saleTicket {
static OSSpinLock ticketLock = OS_SPINLOCK_INIT;
OSSpinLockLock(&ticketLock);
[super saleTicket];
OSSpinLockUnlock(&ticketLock);
}
@implementation OSSpinLockDemo2
static OSSpinLock moneyLock_;
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
moneyLock_ = 0;
});
}
- (void)__drawMoney {
OSSpinLockLock(&moneyLock_);
[super __drawMoney];
OSSpinLockUnlock(&moneyLock_);
}
- (void)__saveMoney {
OSSpinLockLock(&moneyLock_);
[super __saveMoney];
OSSpinLockUnlock(&moneyLock_);
}