在iOS10之后apple废弃了
OSSpinLock
自旋锁,使用os_unfair_lock
来替代。
在OSSpinLock
的api注释中明确指出这是一个自旋锁,那么它的替代方案是一把什么类型的锁呢?
我们知道自旋锁加锁的时候,等待锁的线程处于忙等状态,并且占用着CPU的资源。而互斥锁加锁的时候,等待锁的线程处于休眠状态,不会占用CPU的资源。
那么我们探就加锁状态下的等待锁的线程的状态就可以得出os_unfair_lock
这把锁的类型。
探究os_unfair_lock的类型
依然使用卖票的经典案例,分别使用OSSpinLock
和os_unfair_lock
加锁,当第二条线程执行到加锁代码的时候,是处于等待锁的状态,这个时候我们通过汇编代码窥探等待锁的线程的状态,得出其是自旋锁还是互斥锁。
依然使用中提到的卖票的案例。
#import "ViewController.h"@interface ViewController ()@property (nonatomic, assign) int ticketCount;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // 初始化锁 // ... [self ticket];}- (void)saleTicket { // 加锁 // ... int oldTicketCount = _ticketCount; sleep(1); //让线程睡眠1秒 更能体现多线程的隐患 oldTicketCount --; _ticketCount = oldTicketCount; NSLog(@"剩余的票数%d",_ticketCount); // 解锁 // ...}- (void)ticket { self.ticketCount = 20; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } }); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } }); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } }); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } });}复制代码
在Xcode中对加锁代码进行断点,然后点击任务栏Debug->Debug Worlflow->Always Show Disassembly
。在xcode的lldb下使用si命令让汇编代码一步一步的执行,观察等待锁的线程状态:
我们发现线程进入了上述的状态,这期是相当于一个while循环。这个循环等到自旋锁解锁之后进入下面的代码继续执行。这就是自旋锁忙等的状态。
下面我们来看使用os_unfair_lock
的情况,使用它在代码注释的地方进行初始化,并且进行加锁和解锁,重复上述操作进行观察。
当汇编代码执行到这一行的时候不再继续往下执行,断点也失去了作用。这是因为syscall
调用了系统内核的函数,使得线程进入休眠状态,不再占用CPU资源。所以根据上面描述的自旋锁和互斥锁的区别os_unfair_lock
属于互斥锁。
自旋锁和互斥锁的比较
当预计线程等待锁的时间很短,或者加锁的代码(临界区)经常被调用,但竞争情况很少发生,再或者CPU资源不紧张,拥有多核处理器的时候使用自旋锁比较合适。
而当预计线程等待锁的时间较长,CPU是单核处理器,或者临界区有IO操作,或者临界区代码复杂或者循环量大,临界区竞争非常激烈的时候使用互斥锁比较合适
总结
在iOS10之后apple已经不再建议使用OSSpinLock
自旋锁了,它的替代方案是一个互斥锁,所以一般情况下我们使用互斥锁来解决线程同步的问题才是比较合理的。