1、OSSpinLock(自旋锁)
1.1、OSSpinLock简介
1.2.1、自旋
OSSpinLock叫做“自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源,类似于加锁后,等待的线程会执行一个while循环:
while(已加锁);
1.2.2、可能会出现优先级反转问题
目前已经不再安全(iOS10之后已经弃用),可能会出现优先级反转问题,如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。
原因是系统会使用时间片轮转调度算法来调度线程,给不同的线程分配时间来执行各自的任务,优先级高的线程分配较多的时间,优先级小的线程分配较少的时间。
比如有两个线程thread1和thread2,thread1的优先级比较高,当thread2给任务加锁后,正在执行的过程中,thread1执行此任务,发现已经加锁,开始“自旋”,而由于thread1的优先级比较高,系统会分配更多的时间给thread1,就会导致thread2没时间完成任务,也就无法解锁,导致代码无法继续执行。
1.2、OSSpinLock使用
使用时需要导入头文件:
#import <libkern/OSAtomic.h>
有如下API:
- (void)spinLockAPI {
// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
// 尝试加锁
OSSpinLockTry(&lock);
// 加锁
OSSpinLockLock(&lock);
// 解锁
OSSpinLockUnlock(&lock);
}
卖票问题使用OSSpinLock代码如下:
- (void)saleTicket {
// 加锁
OSSpinLockLock(&_ticketLock);
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"剩余%d张票", self.ticketsCount);
// 解锁
OSSpinLockUnlock(&_ticketLock);
}
在任务开始时执行加锁方法,在任务结束时执行解锁方法,当其他线程执行任务时,发现_ticketLock已经加锁,就会开始等待,等解锁后再加锁并执行任务,这样就保证了线程的同步。
2、os_unfair_lock
1、os_unfair_lock简介
os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持。从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等。使用时需要导入头文件:
#import <os/lock.h>
有如下API:
- (void)unfairLockAPI {
// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// 尝试加锁
BOOL result = os_unfair_lock_trylock(&lock);
// 加锁
os_unfair_lock_lock(&lock);
// 解锁
os_unfair_lock_unlock(&lock);
}
使用方式和OSSpinLock相同:
- (void)__saleTicket {
os_unfair_lock_lock(&_ticketLock);
[super __saleTicket];
os_unfair_lock_unlock(&_ticketLock);
}
2、死锁问题
加锁后需要解锁,如果不解锁就会造成任务一直不能再次执行,造成死锁:
- (void)__drawMoney {
os_unfair_lock_lock(&_moneyLock);
[super __drawMoney];
//没有解锁
}
3、自旋锁原理探究
添加多个线程访问一个加锁任务:
- (void)ticketsTest {
for (int i = 0; i < 10; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(__saleTicket) object:nil] start];
}
}
- (void)__saleTicket {
OSSpinLockLock(&_ticketLock);
[super __saleTicket];
OSSpinLockUnlock(&_ticketLock);
}
在OSSpinLockLock方法处添加断点,在第一条线程调用时,过掉断点,当第二条线程调用时,查看汇编代码(Debug - Debug workflow - Always show Disassembly)
在控制台输入si指令,逐条查看汇编代码运行过程:
(lldb) si
先执行方法OSSpinLockLock方法:
0x10295a661 <+33>: callq 0x10295bf3e; symbol stub for: OSSpinLockLock
之后执行_OSSpinLockLockSlow方法:
0x7fff60335d4a <+11>: jne 0x7fff60336261; _OSSpinLockLockSlow
在内部会进行循环调用,每次执行+41行时都会跳转到+14行开始循环。
0x7fff6033626f <+14>: movl (%rdi), %eax
......
......
......
0x7fff6033628a <+41>: jmp 0x7fff6033626f; <+14>
4、互斥锁原理探究
同样添加多个线程访问一个加锁任务:
- (void)ticketsTest {
for (int i = 0; i < 10; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(__saleTicket) object:nil] start];
}
}
- (void)__saleTicket {
os_unfair_lock_lock(&_ticketLock);
[super __saleTicket];
os_unfair_lock_lock(&_ticketLock);
}
同样在os_unfair_lock_lock方法处添加断点,在第一条线程调用时,过掉断点,当第二条线程调用时,查看汇编代码,在控制台输入si指令,逐条查看汇编代码运行过程:
(lldb) si
先执行os_unfair_lock_lock方法:
0x1018103d1 <+33>: callq 0x101810f9a; symbol stub for: os_unfair_lock_lock
之后执行_os_unfair_lock_lock_slow方法:
0x7fff603335fc <+19>: jmp 0x7fff60334023; _os_unfair_lock_lock_slow
之后执行__ulock_wait方法:
0x7fff603340c0 <+157>: callq 0x7fff603396c8; symbol stub for: __ulock_wait
最后执行syscall方法:
0x7fff603049ec <+8>: syscall
执行完syscall方法后,断点自动过掉了,是因为线程开始休眠了。
所以互斥锁的加锁原理是让线程休眠。
(pthread_mutex原理与os_unfair_lock相同,最后都会调用syscall方法)