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