Objective-C中的同步线程的锁

概述

在多线程编程中往往会遇到多个线程同时访问共享的资源,这种情况我们需要通过同步线程来避免。也就是给线程加锁。

因为Objective-CC语言的超集。,严格的来说是真超集。所以C语言当中的pthread互斥锁在Objective-C中也可以使用,但是Objective-C中定义了本身自己的锁对象和锁协议,所以本篇介绍Objective-C中的锁。

NSLock

NSLocking协议

@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

后面介绍的几种锁类型NSLock,NSConditionLock,NSRecursiveLock,都实现了该协议可以统一用lockunlock进行加锁与解锁。


NSLock使用

    NSLock *lock = [[NSLock alloc] init];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];                //获取锁
        NSLog(@"lock success");
        sleep(5);
        NSLog(@"sleep end");
        [lock unlock];              //放弃之前获取的锁
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];               //获取锁,如果获取不到阻塞当前线程直到获取到锁
        NSLog(@"remove lock");
        [lock unlock];             //放弃获取到的锁
    });

输出结果

 lock success
 sleep end
 remove lock

NSLock对象发送lock消息时先检查能不能获取到这个锁,如果此时在其他线程中正在使用这个锁那么阻塞当前线程一直等待其他线程使用完这个锁unlock放弃这个锁后,才可以在自己当前的线程重新获取到,线程恢复。如果一直获取不到这个锁那么线程将一直阻塞,所以lockunlock这两个方法一定要成对出现。当然还有不阻塞调用锁的线程方法tryLocklockBeforeDate:


tryLock

tryLock尝试获取锁,如果获取不到返回NO,反之YES,不会阻塞当前调用它的线程

    NSLock *lock = [[NSLock alloc] init];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];                //获取锁
        NSLog(@"lock success");
        sleep(5);
        NSLog(@"sleep end");
        [lock unlock];              //放弃之前获取的锁
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        if ([lock tryLock])         //尝试获取锁,如果获取不到返回NO,不会阻塞该线程
        {
            NSLog(@"remove lock");
            [lock unlock];
        }
        else{
            NSLog(@"obtain failure");
        }
    });

输出结果

lock success
obtain failure
sleep end

获取失败是因为当前锁正在其他线程中使用。如果当前锁没有使用那么会返回YES

lockBeforeDate

lockBeforeDate阻塞调用它的线程,如果给定时间内不能够获取锁那么放弃获取并恢复线程。

    NSLock *lock = [[NSLock alloc] init];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];                //获取锁
        NSLog(@"lock success");
        sleep(5);
        NSLog(@"sleep end");
        [lock unlock];              //放弃之前获取的锁
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];
        if ([lock lockBeforeDate:date]) //尝试在未来的3s内获取锁,并阻塞该线程,如果3s内获取不到恢复线程
        {
            NSLog(@"remove lock");
            [lock unlock];
        }
        else{
            NSLog(@"obtain failure");
        }
    });

输出结果

 lock success
 obtain failure
 sleep end

如果3s内获取不到则返回NO,否则返回YES

@synchronized

synchronized对一个对象提供锁定和解锁机制。synchronized会阻塞调用它的线程

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        @synchronized (self) {
            [self logging:@"lock success"];
            sleep(5);
            [self logging:@"sleep end"];
        }
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        @synchronized (self)    //等到上面线程执行完后才回执行下面的代码,此时会阻塞当前线程
        {
            [self logging:@"remove lock"];
        }
    });

输出结果

 lock success
 sleep end
 remove lock

上面对自身对象进行加锁直到上面第一个GCD全部执行结束才会执行下面加锁的代码。

注意

Objective-C类自身也是对象所以可以对这些类对象使用synchronized,此时是对整个对象类型进行线程同步。例如:

    @synchronized ([self class]) {
    }

NSConditionLock

NSConditionLock条件锁,获取锁时必须与之前设置解锁的条件一样时才可以获取到,否则当前线程一直阻塞,直到条件一致时线程才可以恢复。

最典型的例子就是生产者-消费者场景,当数据为空时生产者生产数据此时消费者不能够获取数据,生产者生产数据后,消费者处理数据,直到消费者处理所有数据后,数据又为空,此时生产者继续生产数据。示例代码如下:

    enum {NO_DATA_IN_QUEUE = 0,HAS_DATA_IN_QUEUE};
    NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:NO_DATA_IN_QUEUE];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES) {
            [conditionLock lockWhenCondition:NO_DATA_IN_QUEUE];
            NSLog(@"insert data");
            sleep(3);
            NSLog(@"intset success");
            [conditionLock unlockWithCondition:HAS_DATA_IN_QUEUE];
        }
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES) {
            [conditionLock lockWhenCondition:HAS_DATA_IN_QUEUE];
            NSLog(@"delete data");
            sleep(3);
            NSLog(@"delete success");
            [conditionLock unlockWithCondition:NO_DATA_IN_QUEUE];
        }
    });

输出结果:

 insert data
 intset success
 delete data
 delete success
 insert data
 intset success
 delete data
 delete success
 ...

NSRecursiveLock

NSRecursiveLock递归锁,当我们对一个递归函数同步线程时会在同一个线程多次获取锁,导致线程死锁,NSRecursiveLock可以在同一个线程多次获取锁不会死锁。

NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

       static void (^recursive)(int count);
        recursive = ^(int count){
            [recursiveLock lock];
            if (count>0) {
                NSLog(@"success lock");
                sleep(3);
                recursive(--count);
            }
            [recursiveLock unlock];
        };
        recursive(3);

    });

上面这种情况如果使用NSLock在没有解锁时继续获取锁,就会造成死锁导致线程一致堵塞。

NSDistributedLock

NSDistributedLock是Mac多线程开发中的互斥锁解决方案,在此不多做介绍。