iOS -锁-NSLock

作者: xxxxxxxx_123 | 来源:发表于2020-03-13 16:53 被阅读0次

NSLockiOS开发中最基础的锁。它继承自NSObject,遵守NSLocking协议。用于处理线程安全问题。

下面我们来看一个例子:

for (int i = 0; i < 200000; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        self.mArray = [NSMutableArray array];
    });
}

运行该程序会崩溃,这是因为,我们在不断地创建arraymArray在不断的赋新值,释放旧值,这个时候多线程操作就会可能存在值已经被释放了,而其他线程还在操作,此时就会发生崩溃。此时就需要我们对程序加锁。将上述程序改成如下:

NSLock *lock = [[NSLock alloc] init];
for (int i = 0; i < 200000; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];
        self.mArray = [NSMutableArray array];
        [lock unlock];
    });
}

程序就能正常运行了,这是因为此时,每一条线程执行self.mArray = [NSMutableArray array]的前后,都会有获取锁释放锁的过程,此时这句代码是在线程安全的情况下执行的,所以并没有异常问题。

那么NSLock到底做了什么?附上一份Objective-C的源码:

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name;

@end

以上方法只能看到定义,并不能看到实现,那我们再通过swift的开源代码来看看:

open class NSLock: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
    private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
    private var timeoutMutex = _MutexPointer.allocate(capacity: 1)

    public override init() {
        pthread_mutex_init(mutex, nil)
        pthread_cond_init(timeoutCond, nil)
        pthread_mutex_init(timeoutMutex, nil)
    }
    
    deinit {
        pthread_mutex_destroy(mutex)
        mutex.deinitialize(count: 1)
        mutex.deallocate()
        deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
    }
    
    // 获取锁
    open func lock() {
        pthread_mutex_lock(mutex)
    }

    // 释放锁
    open func unlock() {
        pthread_mutex_unlock(mutex)

        // Wakeup any threads waiting in lock(before:)
        pthread_mutex_lock(timeoutMutex)
        pthread_cond_broadcast(timeoutCond)
        pthread_mutex_unlock(timeoutMutex)
    }
    
    // 尝试加锁,不会阻塞线程。true则加锁成功。
    // false则失败,说明其他线程在加锁中这个方法无论如何都会立即返回。
    open func `try`() -> Bool {
        return pthread_mutex_trylock(mutex) == 0
    }
    
    // 尝试在指定NSDate之前加锁,会阻塞线程。true则加锁成功。
    // false则失败,说明其他线程在加锁中这个方法无论如何都会立即返回。
    open func lock(before limit: Date) -> Bool {
        if pthread_mutex_trylock(mutex) == 0 {
            return true
        }
        return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
    }

    open var name: String?
}

从源码上看NSLock其实只是对pthread_mutex做了一层简单的封装。它属于互斥锁的一种。当一个线程进行访问的时候,该线程获得锁,其他线程进行访问的时候,将被操作系统挂起,直到该线程释放锁,其他线程才能对其进行访问,从而却确保了线程安全。

- (void)nslockTest {
    NSLock *lock = [[NSLock alloc] init];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"进入线程1");
        sleep(2);
        [lock lock];
        NSLog(@"执行任务1");
        [lock unlock];
        NSLog(@"退出线程1");
    });
        
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"进入线程2");
        [lock lock];
        sleep(5);
        NSLog(@"执行任务2");
        [lock unlock];
        NSLog(@"退出线程2");
    });
}

运行程序,控制台输出:

image

通过结果,我们可以看到虽然程序先进入线程1,但是由于我们在执行lock加入了延迟,由于是并发操作,所以紧接着,会进入线程2,线程2可以立即执行lock操作,虽然我们紧接着sleep了5秒钟,但是由于锁已经被线程2占用,并不会去执行线程1的操作,此时线程1就被阻塞了,只有等到线程2执行完成解锁之后才会进入线程1执行任务。这也就完美的体现了互斥锁的特性。

需要注意的是,NSLock在使用不当的时候会造成堵塞线程。

相关文章

网友评论

    本文标题:iOS -锁-NSLock

    本文链接:https://www.haomeiwen.com/subject/gzpdshtx.html