316,iOS的线程锁的详解

之前讲过锁有两种形式:
一种是忙等就像OSSpinLock这种自旋锁,
一种是让线程睡眠。os_unfair_lock就是让线程睡眠,所以它避免了自旋锁导致的优先级反转问题

在iOS开发中,不可避免的需要使用到多线程。但是使用多线程的过程中,如果使用不当,就会造成数据混乱,那要怎么保证多线程使用中不会因为访问同一个内存空间而造成数据混乱呢?这个时候锁(LOCK)就该闪亮登场了。本文会从以下几个方面介绍锁,希望对大家有帮助:

一、 锁是什么以及为什么需要?

1)锁是什么以及为什么需要?
2)iOS中都有哪些锁?
3)锁的使用?

锁是一种保证多线程并发执行安全的方式,避免 同一时间,多个线程同时读取并修改一个值而产 生混乱。

先来直观感受一下多线程不加锁的混乱,我们以火车站卖票为例,假设有20张票,同时有两个窗口售票:

- (void)ticketTest{
    self.ticketsCount = 20;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (NSInteger i = 0; i < 2; i++) {
        dispatch_async(queue, ^{
            for (int i = 0; i < 10; i++) {
                [self sellingTickets];//多线程售票
            }
        });
    }
}

- (void)sellingTickets{
    NSInteger oldMoney = self.ticketsCount;
    sleep(.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    NSLog(@"当前剩余票数-> %ld", oldMoney);
}

此时得到的结果是这样的

2019-01-08 15:08:55.954142+0800 LockDemo[19360:817602] 当前剩余票数-> 19
2019-01-08 15:08:55.954142+0800 LockDemo[19360:817605] 当前剩余票数-> 19
2019-01-08 15:08:55.954349+0800 LockDemo[19360:817605] 当前剩余票数-> 18
2019-01-08 15:08:55.954349+0800 LockDemo[19360:817602] 当前剩余票数-> 18
2019-01-08 15:08:55.954460+0800 LockDemo[19360:817605] 当前剩余票数-> 17
2019-01-08 15:08:55.954460+0800 LockDemo[19360:817602] 当前剩余票数-> 16
2019-01-08 15:08:55.954543+0800 LockDemo[19360:817605] 当前剩余票数-> 15
2019-01-08 15:08:55.954622+0800 LockDemo[19360:817602] 当前剩余票数-> 14
2019-01-08 15:08:55.954674+0800 LockDemo[19360:817605] 当前剩余票数-> 13
2019-01-08 15:08:55.955669+0800 LockDemo[19360:817602] 当前剩余票数-> 12
2019-01-08 15:08:55.956128+0800 LockDemo[19360:817605] 当前剩余票数-> 11
2019-01-08 15:08:55.956876+0800 LockDemo[19360:817602] 当前剩余票数-> 10
2019-01-08 15:08:55.957357+0800 LockDemo[19360:817605] 当前剩余票数-> 9
2019-01-08 15:08:55.957550+0800 LockDemo[19360:817602] 当前剩余票数-> 8
2019-01-08 15:08:55.957724+0800 LockDemo[19360:817605] 当前剩余票数-> 7
2019-01-08 15:08:55.957901+0800 LockDemo[19360:817602] 当前剩余票数-> 6
2019-01-08 15:08:55.958065+0800 LockDemo[19360:817605] 当前剩余票数-> 5
2019-01-08 15:08:55.958221+0800 LockDemo[19360:817602] 当前剩余票数-> 4
2019-01-08 15:08:55.958391+0800 LockDemo[19360:817605] 当前剩余票数-> 3
2019-01-08 15:08:55.958565+0800 LockDemo[19360:817602] 当前剩余票数-> 2

不加锁我们看到很明显的发生了混乱。我们对锁的需要也就不言而喻了。

二、 iOS中都有哪些锁?

从大的方向讲有两种锁:互斥锁、自旋锁。这两种类型下分别有自己对应的锁

image.png

互斥锁和自旋锁的对比:
这两种锁的相同点不必多说,都可以避免多线程访问同一个值发生混乱,重点说一下两种的不同点:

互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁, 则等待资源的线程会被唤醒

自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁, 则等待资源的线程会立即执行

自旋锁的特点:

1.自旋锁的性能高于互斥锁,因为响应速度快
2.自旋锁虽然会一直自旋等待获取锁,但不会一直占用CPU,超过了操作系统分配的时间片会被强制挂起
3.自旋锁如果不能保证所有线程都是同一优先级,则可能造成死锁。

因为以上的特点,自旋锁和互斥锁也有不同的使用场景:

多核处理器情况下: 如果预计线程等待锁的时间比较短,短到比线程两次切换上下文的时间还要少的情况下,自旋锁是更好的选择。
如果时间比较长,则互斥锁是比较好的选择。 单核处理器情况下: 不建议使用自旋锁。

dispatch_semaphore

dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时, 它的性能比pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。

使用也是非常的简单又方便,会用下面几个函数就可以:

dispatch_semaphore_create(long value),//创建锁
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)//接受信号,信号量加1
dispatch_semaphore_signal(dispatch_semaphore_t dsema),//发送信号,信号量减1
 -(void) dispatch_semaphoreDemo
{
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
    // 如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
    // dispatch_time_t  t = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000); 1ms
    // DISPATCH_TIME_NOW没有超时时间 立即执行后面的代码  DISPATCH_TIME_FOREVER一直等待 直到获得信号量才执行后面的代码

    dispatch_semaphore_t signal = dispatch_semaphore_create(1); //传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句
    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"线程1 等待ing");
        dispatch_semaphore_wait(signal, overTime); //signal 值 -1
        NSLog(@"执行线程1");
        dispatch_semaphore_signal(signal); //signal 值 +1
        NSLog(@"线程1 发送信号");
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//        sleep(1);
        NSLog(@"线程2 等待ing");
        dispatch_semaphore_wait(signal, overTime);
        NSLog(@"执行线程2");
        dispatch_semaphore_signal(signal);
        NSLog(@"线程2 发送信号");
    });
}

初始创建了一个信号量为1的signal,线程1和线程2的执行顺序不一定,但不管哪个先执行,另一个都不会再去抢占,因为此时的信号量为0,直到当前线程执行完成,发送一个新的信号,信号量为1,此时这个等待线程才能继续执行。

4.pthread_mutex

pthread_mutex是c底层的线程锁,关于pthread的各种同步机制,感兴趣的可以看看这篇文章pthread的各种同步机制,可谓讲的相当全面了

/*
 PTHREAD_MUTEX_NORMAL 缺省类型,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。
 PTHREAD_MUTEX_ERRORCHECK 检错锁,如果同一个线程请求同一个锁,则返回 EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。
 PTHREAD_MUTEX_RECURSIVE 递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。
 PTHREAD_MUTEX_DEFAULT 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。
 */
- (void)pthreadmutexDemo
{
    //定义pthreadmutex锁的属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);//定义锁的属性PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE
    
    //创建锁
    static pthread_mutex_t pLock;
    pthread_mutex_init(&pLock, &attr);
    
    //static pthread_mutex_t pLock = PTHREAD_MUTEX_INITIALIZER;//另一种创建锁的方式,不需要设置属性的时候

    //1.线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 准备上锁");
        pthread_mutex_lock(&pLock);
        sleep(3);
        NSLog(@"执行线程1");
        pthread_mutex_unlock(&pLock);
    });

    //2.线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 准备上锁");
        pthread_mutex_lock(&pLock);
        NSLog(@"执行线程2");
        pthread_mutex_unlock(&pLock);
    });
    
    //3.线程3  测试递归锁   需要定义锁的属性为PTHREAD_MUTEX_RECURSIVE
    NSMutableArray *arrar = [NSMutableArray array];
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            pthread_mutex_lock(&pLock);
            if (value > 0) {
                NSLog(@"value: %d %@", value,arrar);
                [arrar addObject:@(value)];
                RecursiveBlock(value - 1);
            }
        };
        RecursiveBlock(4);
        
    //NSRecursiveLock基于PTHREAD_MUTEX_RECURSIVE的pthread_mutex_lock封装
}

5.NSLock、NSCondition、NSConditionLock、NSRecursiveLock

以上所列四种锁全部是基于pthread_mutex封装的面向对象的锁。这几种锁都遵守NSCopying协议,此协议中提供了加锁和解锁方法。

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

NSLock的使用,非常简单

- (void)NSLockDemo
{
    // 初始化锁
    NSLock *lock = [[NSLock alloc] init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 尝试加锁ing...");
        [lock lock]; // 加锁,也有tryLock方法
        sleep(3);//睡眠3秒
        NSLog(@"执行线程1");
        [lock unlock];// 解锁
        NSLog(@"线程1解锁成功");
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 尝试加锁ing...");
        BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];//超时时间之前不能获得锁就返回NO
        if (x) {
            NSLog(@"执行线程2");
            [lock unlock];
            NSLog(@"线程2解锁成功");
        }else{
            NSLog(@"线程2加锁失败");
        }
    });
}

这里NSCodition和NSConditionLock是比较有意思的两个锁,首先看一下NSCondition,它的使用和dispatch_semaphore有异曲同工之妙。NSCondition提供以下几个方法:

- (void)wait;   // 线程等待
- (BOOL)waitUntilDate:(NSDate *)limit;  // 设置线程等待时间,过了这个时间就会自动执行后面的代码
- (void)signal; // 唤醒一个设置为wait等待的线程
- (void)broadcast;  // 唤醒所有设置为wait等待的线程

前两个是使线程等待,后两个是唤醒等待的线程。

- (void)NSConditionDemo
{
   //比如删除数组中的所有元素,等数组有元素时再去删除
    NSCondition *lock = [[NSCondition alloc] init];
    NSMutableArray *array = [[NSMutableArray alloc] init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        while (!array.count) {
            [lock wait];
        }
        [array removeAllObjects];
        NSLog(@"array removeAllObjects");
        [lock unlock];
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);//以保证让线程2的代码后执行
        [lock lock];
        [array addObject:@1];
        NSLog(@"array addObject:@1");
        [lock signal];
        [lock unlock];
    });
}

NSConditionLock提供以下几个方法,相比其他锁多了condition,也就是传说中的条件锁:

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

使用很简单

- (void)NSConditionLockDemo
{
    NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if([cLock tryLockWhenCondition:0]){
            NSLog(@"线程1");
            [cLock unlockWithCondition:1];
        }else{
            NSLog(@"失败");
        }
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:3];
        NSLog(@"线程2");
        [cLock unlockWithCondition:2];
    });
    
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:1];
        NSLog(@"线程3");
        [cLock unlockWithCondition:3];
    });
}

初始化NSConditionLock时需要指定当前condition的值,比如上面例子中初始化的condition设置为0,三个线程加锁时会匹配这个condition,只有是0的才能加锁成功。解锁时也需要给定一个condition,可以自己随便定义,然后继续寻找匹配condition的线程加锁…依此类推直到加解锁完所有任务。根据NSConditionLock的这种特性,可以用来做多线程任务之间的依赖。

6.@synchronized

@synchronized可能是我们平常用的比较多的加锁方式,为什么呢?因为它使用起来最简单。

- (void)synchronizedDemo
{
    @synchronized (self) {
        [self sellingTickets];
    }
}

你可以给任何 Objective-C 对象上加个 @synchronized。那么我们也可以在上面的例子中用 @synchronized(self.view) 来替代 @synchronized(self),效果是相同的。
对这种锁内部实现原理感兴趣的,可以看看这篇文章关于@synchronized,这儿比你想知道的还要多
那这么简单的语法下系统为我们做了什么呢?我们把这段代码翻译成汇编代码(xcode可以自动翻译,在Product–Perform Action–Assemble “文件名”,可以把仍任意oc代码翻译成汇编代码),看到
@synchronized (self) {
}
实际上是调用了_objc_sync_enter和_objc_sync_exit这一对方法

image.png

我们可以在objc源码中查找到这两个方法的实现,具体的原理就不展开了,有兴趣看看上面的文章吧。
下面是对上面介绍的各种锁的执行效率的定性分析(只代表加解锁的效率,比如执行1万次加解锁的操作耗费的时间)

image.png

如上,就是本次介绍的iOS锁的知识。最后以一个小小的总结完成锁的介绍:
1、所有的锁基本都是创建锁、加锁、等待、解锁的流程,所以并不复杂。
2.如果追求锁的极致性能,可以考虑更偏底层实现的pthread_mutex互斥锁以及信号量的方式。
3.@synchronized的效率最低,但是它使用最方便,所以如果没有性能瓶颈的话使用它也不错。

二,iOS的OSSpinLock与os_unfair_lock

首先说说自旋锁OSSpinLock,它的原理其实就是一个死循环。

//伪代码
bool lock = false; // 一开始没有锁上,任何线程都可以申请锁
do {
    while(lock); // 如果 lock 为 true 就一直死循环,相当于申请锁
    lock = true; // 挂上锁,这样别的线程就无法获得锁
    Critical section  // 临界区
    lock = false; // 相当于释放锁,这样别的线程可以进入临界区
    Reminder section // 不需要锁保护的代码
}

但不幸的是OSSpinLock已经在iOS10被苹果弃用,因为它存在优先级反转的问题,故不再细讲,只需要知道其大概的原理就OK。

优先级反转:
发生在低优先级线程拿到锁时,高优先级线程进入忙等(busy-wait)状态,消耗大量 CPU 时间,
从而导致低优先级线程拿不到 CPU 时间,也就无法完成任务并释放锁。

那为什么忙等会导致低优先级线程拿不到时间片?

现代操作系统在管理普通线程时,通常采用时间片轮转算法(Round Robin,简称 RR)。每个线程会
被分配一段时间片(quantum),通常在 10-100 毫秒左右。当线程用完属于自己的时间片以后,就会
被操作系统挂起,放入等待队列中,直到下一次被分配时间片。

  • 用锁的场景:多条线程存在同时操作(删、查、读、写)同一个文件or对象or变量。如果不是同时或者不是同一个那就不用加锁了。

  • 介绍:OSSpinLock是在iOS10前还算比较常见的一钟锁,其是"忙等"的锁,所以适用于轻量级的操作,比如基本数据类型的加减,如int 的-1,+1操作,“忙等”的锁,大致的解析就是会一直 while(目标锁还未释放),然后一直执行,所以会很耗cpu的性能

  • 还有另外一种锁的实现,是将线程状态改成休眠,然后等待唤醒。这种其实也不是很省资源,因为线程之间的切换也是非常耗性能的,大概需要20毫秒的时间。

  • 隐患:会出现优先级翻转的情况.比如线程1优先级比较高,线程2优先级比较低,然后在某一时刻是线程2先获取到锁,所以先是线程2加锁,这时候,线程1就在while(目标锁还未释放),这个状态,但因为线程1优先级比较高,所以系统分配的时间比较多,有可能会没有分配时间给线程2执行后续的操作(需要做的任务和解锁)了,这时候就会造成死锁。

但如果是线程休眠的情况,在优先级高的线程休眠后,优先级比较低的线程会给系统调用,所以不会有死锁的情况

  • 使用方法
//导入头文件
#import <libkern/OSAtomic.h>

//因为多个线程要公用一把锁,所以定义为属性,因为是c的,所以用assign
@property (assign, nonatomic) OSSpinLock mLock;

//创建
self. mLock = OS_SPINLOCK_INIT;

//加锁
OSSpinLockLock(&_moneyLock);

// <···你的代码···>

//解锁
OSSpinLockUnlock(&_moneyLock);

oc代码

image.png

这也就是我为什么在前面说iOS10之前自旋锁比较常见,其实在10之后,系统已经修改了OSSpinLock的实现方式了,改用了os_unfair_lock 来实现了

os_unfair_lock

介绍:

这是苹果iOS10之后推出的新的取代OSSpinLock的锁。虽然是替代OSSpinLock的,但os_unfair_lock并不是自旋锁,根据苹果的官方文档可以看到其实它是一个互斥锁。
os_unfair_lock使用起来非常简单,首先需要引入系统库 #import <os/lock.h>

_unfairLock = OS_UNFAIR_LOCK_INIT;  //唯一的初始化方法
- (void)os_unfair_lockDemo
{
    os_unfair_lock_lock(&_unfairLock);          //加锁
//    os_unfair_lock_trylock(&_unfairLock);  //尝试加锁,加锁失败返回NO,成功返回YES
    [self   sellingTickets];
    os_unfair_lock_unlock(&_unfairLock);    //解锁
}

os_unfair_lock是在iOS10之后为了替代自旋锁OSSpinLock而诞生的,主要是通过线程休眠的方式来继续加锁,而不是一个“忙等”的锁。猜测是为了解决自旋锁的优先级反转的问题。

OC代码

image.png

使用:

#import <os/lock.h>

@property (assign, nonatomic) os_unfair_lock mLock;

self.mLock = OS_UNFAIR_LOCK_INIT;

os_unfair_lock_lock(&_mLock);
os_unfair_lock_unlock(&_mLock);

通过查看汇编代码来看2钟锁的不同

注意:要把系统调到10.0以下,我测试自旋锁的时候调到了9.0
初步代码
//
//  ViewController.m
//  OSSpinLockAndunfairlock
//
//  Created by LJP on 2020/2/29.
//  Copyright © 2020 L. All rights reserved.
//

#import "ViewController.h"
#import <libkern/OSAtomic.h>
#import <os/lock.h>

@interface ViewController ()

@property (assign, nonatomic) OSSpinLock mSpinLock;

//@property (assign, nonatomic) os_unfair_lock mUnfairLock;

@property (assign, nonatomic) NSInteger mCount;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.mSpinLock = OS_SPINLOCK_INIT;
    self.mCount = 30;

    NSLog(@"开始");
    [self test];
}

- (void)test {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self subCount];
        }
    });

    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self subCount];
        }
    });

    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self subCount];
        }
    });
}

- (void)subCount {
    sleep(0.5);
    self.mCount = self.mCount - 1;

    NSLog(@"mCount == %ld   name == %@ ", (long)self.mCount, [NSThread currentThread]);
}

@end

运行的结果如下

image.png

从控制图里可以看出如果不加锁顺序是会乱的,并且有可能最终的结果不是0

加锁之后
image.png

执行几次后,顺序没有乱

下面就是观察汇编代码了

  • 首先把睡眠时间调高一点,然后运行
image.png

可以看到,是选运行线程6 再到线程8 再到线程9

  • 调出显示汇编指令的面板
image.png
  • si指令 si是让汇编指令一条一条的向下走
image.png

当你多敲几次后你会发现,他会一直在这个范围里走来走去

  • jne指令 其实就是一个判断语句,如果符合条件,就跳去相应的行数
image.png

所以说自旋锁是相当于 while 一直在循环等待

下面开始测试os_unfair_lock
  • 先把版本调高
  • 然后和自旋一样的操作
image.png

多次测试之后你会发现 走到syscall里就会跳会vc的画面,不会再有下一步的si了。

image.png

猜测是系统调用线程进入休眠了

总结

其实2个锁都耗性能,各有优劣,但可能是因为自旋锁会产生优先级反转,用互斥锁会比较安全

自旋锁在 循环等待的时候会消耗cpu的性能
互斥锁在 cpu线程调度的时候会消耗cpu性能

所以互斥锁,比较适合 临界代码 比较耗时间长的 比如 有网络阻塞 IO 阻塞的情况

自旋锁, 因为一直消耗cpu 所以 一般比较适合 临界代码比较少的 比较适合段时间操作的 比如 从 mutable 对象里面(dictioanry array hashtable) 读写操作的情况

最后的代码

//
//  ViewController.m
//  OSSpinLockAndunfairlock
//
//  Created by LJP on 2020/2/29.
//  Copyright © 2020 L. All rights reserved.
//

#import "ViewController.h"
#import <libkern/OSAtomic.h>
#import <os/lock.h>

@interface ViewController ()

//@property (assign, nonatomic) OSSpinLock mSpinLock;

@property (assign, nonatomic) os_unfair_lock mUnfairLock;

@property (assign, nonatomic) NSInteger mCount;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

//    self.mSpinLock = OS_SPINLOCK_INIT;
    self.mUnfairLock = OS_UNFAIR_LOCK_INIT;
    self.mCount = 30;

    NSLog(@"开始");
    [self test];
}

- (void)test {
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//
//    dispatch_async(queue, ^{
//        for (int i = 0; i < 10; i++) {
//            [self subCount];
//        }
//    });
//
//    dispatch_async(queue, ^{
//        for (int i = 0; i < 10; i++) {
//            [self subCount];
//        }
//    });
//
//    dispatch_async(queue, ^{
//        for (int i = 0; i < 10; i++) {
//            [self subCount];
//        }
//    });
    
    for (int i = 0; i < 10; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(subCount) object:nil] start];
    }
    
}

- (void)subCount {
    os_unfair_lock_lock(&_mUnfairLock);

    sleep(60);

    self.mCount = self.mCount - 1;

    NSLog(@"mCount == %ld   name == %@ ", (long)self.mCount, [NSThread currentThread]);

    os_unfair_lock_unlock(&_mUnfairLock);
}

@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容