ObjC 多线程简析(二)- os_unfair_lock的类型和自旋锁与互斥锁的比较

4,264 阅读3分钟

在iOS10之后apple废弃了OSSpinLock自旋锁,使用os_unfair_lock来替代。

OSSpinLock的api注释中明确指出这是一个自旋锁,那么它的替代方案是一把什么类型的锁呢?

OSSpinLock

我们知道自旋锁加锁的时候,等待锁的线程处于忙等状态,并且占用着CPU的资源。而互斥锁加锁的时候,等待锁的线程处于休眠状态,不会占用CPU的资源。

那么我们探就加锁状态下的等待锁的线程的状态就可以得出os_unfair_lock这把锁的类型。

探究os_unfair_lock的类型

依然使用上一篇中提到的卖票的案例。,分别使用OSSpinLockos_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自旋锁了,它的替代方案是一个互斥锁,所以一般情况下我们使用互斥锁来解决线程同步的问题才是比较合理的。