OC底层原理探索—锁(下)

NSLock

使用NSLock将加锁解锁操作放在正确的位置

image.png

这里NSlock起到了作用,接下来将lock操作放到testMethod内部,见下图:
image.png

这个时候,并没有正场的打印。是因为textMethod后面进行lock加锁,内部又继续调用textMethod,导致重复加锁

对于NSLock锁。被它锁住的区域(代码块)。没解锁之前,不能被其它线程访问。所以外面的for循环 异步并行队列被阻塞了。只能依次执行。但是当将lock操作放入内部,textMethod方法还是处于循环调用状态,这就导致了重复加锁
【结论】NSLock不支持重复加锁

NSRecursiveLock

使用NSRecursiveLock将加锁解锁操作放在正确的位置

image.png

这里NSRecursiveLock起到了作用,接下来将lock操作放到testMethod内部,见下图:
image.png

这里发现只运行了一次就发生了崩溃
【结论】NSRecursiveLock只支持单线程单线程内递归加锁,不支持多线程

NSCondition

NSCondition的对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否执行线程,即线程是否被阻塞

  • [condition lock] 一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只能被访问、修改一次,其他线程的命令需要在lock外等待,直到unlock才能被访问
  • [condition unlock]:与lock同时使用
  • [condition wait] 让当前线程处于等待状态
  • [condition signal] CPU发出信号告诉线程不用在等待,可以继续执行
实例

NSCondition常用与生产销售模型,这里开启多个线程进行产品生产和销售

image.png

这里的生产和消费进行加锁处理,来保证多线程的安全。与此同时,生产与消费之间也存在 库存数目的关系,当库存数目不足时需要[_testCondition wait];进行等待,让消费窗口停止消费,[_testCondition signal]表示库存充足,可以进行消费
image.png

NSLocationLock

NSLocationLock也是一种条件锁,一旦⼀个线程获得锁,其他线程⼀定等待

  • [xxxx lock]; 表示 xxx 期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition) 那它能执⾏此⾏以下代码,如果已经有其他线程获得锁(可能是条件锁,或者⽆条件锁),则等待,直⾄其他线程解锁
  • [xxx lockWhenCondition:A条件]; 表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进⼊代码区,同时置它获得该锁,其他任何线程都将等待它代码的完成,直⾄它解锁
  • [xxx unlockWithCondition:A条件]; 表示释放锁,同时把内部的condition设置为A条件
  • return = [xxx lockWhenCondition:A条件 beforeDate:A时间]; 表示如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函数的⽬的在于可以实现两种状态下的处理

来看下下面的案例


image.png

image.png

这里的打印是32132前面我们可以理解,毕竟2处有个sleep,至于1为什么在后面呢?
是因为在初始化的时候condition传的是2,所以先执行2,至于3它是不受condition的影响。只有执行2的任务,将condition变为1才会执行1

Foundation库锁源码了解

image.png

image.png

我们看到NSLock,NSRecursiveLock,NSCondition都是遵守的NSLocking协议,但是这部分源码是在Foundation框架下,OC并没有开源,但是swift的Foundation框架是开源的,打开Foundation源码

NSLock
全局搜索NSLock,在初始化中,调用了pthread_mutex_init

image.png

lock中调用了pthread_mutex_lock,unlock中调用了pthread_mutex_unlock
image.png

NSRecursiveLock

image.png

NSRecursiveLockNSLock逻辑基本相同,唯一不同的是增加了pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))这行代码,也就是增加了锁的递归性

自定义读写锁(多读单写)

多读单写要求

  • 多读不互斥
  • 读写互斥
  • 写写互斥
  • 不能阻塞主线程
@interface ViewController ()

@property (nonatomic,assign)NSInteger ticketCount;
// 并发队列
@property (nonatomic,strong)dispatch_queue_t safeQueue;
@end


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.ticketCount = 0;
    self.safeQueue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i ++) {
        [self writeticket];
    }

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    for (int i = 0; i < 10; i ++) {
        [self readTicket];
    }
}


- (void)readTicket{
    dispatch_async(dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT), ^{
        dispatch_sync(self.safeQueue, ^{
            NSLog(@"读取:%ld",self.ticketCount);
        });
    });

}
- (void)writeticket{
    
    dispatch_barrier_async(self.safeQueue, ^{
        sleep(2);
        self.ticketCount ++;
        NSLog(@"写入:%ld",self.ticketCount);
    });
}
  • 写入使用dispatch_barrier_async,由于队列是相同的,所以实现了写写互斥
  • 读取首先在外层使用dispatch_async,是为了防止主线程阻塞。内部使用dispatch_sync且队列与写入的队列相同,是为了实现多读不互斥,并且读写互斥
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容