iOS中常见的锁

锁一般用于在多线程中,保证在一段时期内这段代码只能被某一个线程所访问,从而保证线程同步。在iOS中,常用的锁大致有@synchronized,NSLock,NSCondition,NSConditionLock、NSRecursiveLock和dispatch_semaphore等,如下图所示。
image.png

常见锁介绍

锁按照功能分,主要可以分为互斥锁,自旋锁,信号量。

  • 互斥锁禁止多个线程同时进入受保护的代码“临界区”(critical section)。
  • 自旋锁跟互斥锁一样,但是获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。
  • 信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。

    @synchronized

    @synchronized(obj)指令使用的obj为该锁的唯一标识,同一标识的线程互斥。这是通过一个哈希表来实现的,OC 在底层使用了一个互斥锁的数组(你可以理解为锁池),通过对对象去哈希值来得到对应的互斥锁。@synchronized的内部详细原理可以参考关于 @synchronized,这儿比你想知道的还要多
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1,@2, nil];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(1);
    @synchronized(arr) {
        [arr removeLastObject];
        sleep(1);
        NSLog(@"%lu in thread1",arr.count);

    }
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    @synchronized(arr) {
        @synchronized(arr) {
            [arr removeLastObject];
            NSLog(@"%lu in thread2",arr.count);
            sleep(2);
        }
    }
});

输出结果如下:

2017-12-20 14:55:19.646387+0800 Lock[49276:6948973] 1 in thread2
2017-12-20 14:55:22.653366+0800 Lock[49276:6948972] 0 in thread1

NSConditon

NSConditon是一种条件锁。当其他线程的锁收到signal(单发)或者broadcast(多发)时,如果条件为true,则会上锁。

NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [lock lock];
    while (!array.count) {
        [lock wait];
    }
    [array removeLastObject];
    NSLog(@"remove last object %@",array);
    [lock unlock];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [lock lock];
    while (!array.count) {
        [lock wait];
    }
    [array removeLastObject];
    NSLog(@"remove last object %@",array);
    [lock unlock];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(1);
    [lock lock];
    [array addObjectsFromArray:@[@1,@2]];
    NSLog(@"add objects @1,@2");
    [lock broadcast];
    [lock unlock];
});
}

输出如下:

2017-12-20 15:09:27.322078+0800 Lock[67589:7008671] add objects @1,@2
2017-12-20 15:09:27.322539+0800 Lock[67589:7008668] remove last object (
1
)
2017-12-20 15:09:27.322731+0800 Lock[67589:7008669] remove last object (
)

NSConditionLock

NSConditionLock这种条件锁在初始化时可以指定一个条件,在上锁时,只有满足这个条件才能上锁,在解锁时,可以修改这个条件锁的条件。

NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [lock lockWhenCondition:0];
    NSLog(@"locked this lock in thread 1");
    sleep(1);
    [lock unlock];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(2);
    if ([lock tryLockWhenCondition:1]) {
        NSLog(@"locked this lock in thread 2");
        sleep(1);
        [lock unlockWithCondition:0];
        NSLog(@"unlocked this lock in thread 2");
    } else {
        NSLog(@"fail locked this lock in thread 2");
    }
});

输出如下:

2017-12-20 15:16:32.725752+0800 Lock[76697:7037383] locked this lock in thread 2
2017-12-20 15:16:33.726779+0800 Lock[76697:7037383] unlocked this lock in thread 2
2017-12-20 15:16:33.726803+0800 Lock[76697:7037384] locked this lock in thread 1

NSRecursiveLock

递归锁用在需要递归的地方。这种锁不会因为重复上锁而导致死锁。

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    static void (^recursiveBlock)(int);
    recursiveBlock = ^(int value) {
        [lock lock];
        if (value > 0) {
            NSLog(@"value is %d",value);
            recursiveBlock(value-1);
        }
        [lock unlock];
    };
    recursiveBlock(5);
});

输出如下:

2017-12-20 15:21:09.465255+0800 Lock[82850:7057130] value is 5
2017-12-20 15:21:09.465562+0800 Lock[82850:7057130] value is 4
2017-12-20 15:21:09.465739+0800 Lock[82850:7057130] value is 3
2017-12-20 15:21:09.465906+0800 Lock[82850:7057130] value is 2
2017-12-20 15:21:09.466064+0800 Lock[82850:7057130] value is 1

dispatch_semaphore

使用方法非常简单,dispatch_semaphore_create(1)为创建信号,数字表示可以同时几个线程使用信号。为1表示同步使用。如果此处标2就和没设置信号量一样,并发自行运行。如果设置为0,则一律等待overTime时自动释放,所有代码都不执行,理论上也具有同步作用,就是慢点…

dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"线程1 等待ing");
    dispatch_semaphore_wait(signal, timeout); //signal 值 -1
    NSLog(@"线程1 sleep");
    sleep(2);
    NSLog(@"线程1");
    dispatch_semaphore_signal(signal); //signal 值 +1
    NSLog(@"线程1 发送信号");
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"线程2 等待ing");
    dispatch_semaphore_wait(signal, timeout);
    NSLog(@"线程2 sleep");
    sleep(2);
    NSLog(@"线程2");
    dispatch_semaphore_signal(signal);
    NSLog(@"线程2 发送信号");
});

输出如下:

2017-12-20 16:28:19.286668+0800 Lock[70914:7323787] 线程2 等待ing
2017-12-20 16:28:19.286670+0800 Lock[70914:7323790] 线程1 等待ing
2017-12-20 16:28:19.286810+0800 Lock[70914:7323787] 线程2 sleep
2017-12-20 16:28:21.290357+0800 Lock[70914:7323787] 线程2
2017-12-20 16:28:21.290556+0800 Lock[70914:7323787] 线程2 发送信号
2017-12-20 16:28:21.290566+0800 Lock[70914:7323790] 线程1 sleep
2017-12-20 16:28:23.294412+0800 Lock[70914:7323790] 线程1
2017-12-20 16:28:23.294572+0800 Lock[70914:7323790] 线程1 发送信号

##

Author: MrHook
Link: https://bigjar.github.io/2018/01/29/iOS%E4%B8%AD%E5%B8%B8%E8%A7%81%E7%9A%84%E9%94%81/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.