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方法)
