iOS -锁-NSLock

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在使用不当的时候会造成堵塞线程。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 引用自多线程编程指南应用程序里面多个线程的存在引发了多个执行线程安全访问资源的潜在问题。两个线程同时修改同一资源有...
    Mitchell阅读 2,020评论 1 7
  • 转发:LockForiOS 又到了春天挪坑的季节,想起多次被问及到锁的概念,决定好好总结一番。 翻看目前关于 iO...
    Cooci_和谐学习_不急不躁阅读 2,237评论 1 22
  • 转载:谈 iOS 的锁 又到了春天挪坑的季节,想起多次被问及到锁的概念,决定好好总结一番。 翻看目前关于 iOS ...
    小鲲鹏阅读 540评论 0 0
  • 锁是一种同步机制,用于多线程环境中对资源访问的限制iOS中常见锁的性能对比图(摘自:ibireme): iOS锁的...
    LiLS阅读 1,553评论 0 6
  • 翻译:Synchronization 同步 应用程序中存在多个线程会导致潜在的问题,这些问题可能会导致从多个执行线...
    AlexCorleone阅读 2,542评论 0 4