老青菜

iOS NSLock 底层分析

2020-03-29

Foundation框架中,提供了NSLock互斥锁、NSCondition条件变量、NSConditionLock条件锁、NSRecursiveLock递归锁。
这些锁都是在POSIX标准接口的pthread_mutexpthread_cond基础上,增加了面向对象的封装。所以性能上肯定没有直接使用pthread_mutexpthread_cond快。接下来我们来了解下他们的使用方法和原理。

NSLock

NSLockFoundation框架提供的互斥锁,底层使用了POSIXpthread_mutex互斥锁。常用API有:

///加锁
func lock()
///解锁
func unlock()
///尝试加锁,返回是否成功,不阻塞
open func `try`() -> Bool
///在指定时间内一直尝试加锁,返回是否成功
open func lock(before limit: Date) -> Bool

使用也非常简单。

///swift
public func test_lock() {
    NSLog("start")
    self.lock = NSLock()
    for i in 1...5 {
        DispatchQueue.global(qos: .default).async {
            self.lock?.lock(before: Date().advanced(by: 100))
            sleep(5)
            NSLog("\(i):"+Thread.current.description);
            self.lock?.unlock()
        }
    }
    NSLog("end")
}

use mutex

接下来我们来看看NSLock底层实现,在Swift Foundation源码 NSLock.swift文件中,找到以下代码(提取了部分关键代码):

private typealias _MutexPointer = UnsafeMutablePointer<pthread_mutex_t>
private typealias _RecursiveMutexPointer = UnsafeMutablePointer<pthread_mutex_t>
private typealias _ConditionVariablePointer = UnsafeMutablePointer<pthread_cond_t>

open class NSLock: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
    public override init() {
        //初始化互斥锁,没有递归类型的属性
        pthread_mutex_init(mutex, nil)
    }
    open func lock() {
         //使用mutex加锁
        pthread_mutex_lock(mutex)
    }
    open func unlock() {
         //使用mutex解锁
        pthread_mutex_unlock(mutex)
    }
}

NSCondition

NSConditionFoundation框架提供的条件变量,也是一种互斥锁,底层是对POSIXpthread_cond条件变量和pthread_mutex互斥量的封装。
pthread_mutex保证了访问pthread_condlock()操作的线程安全。pthread_cond通过信号量的方式,通知其他线程恢复或者挂起当前线程。常用API有:

///加锁
func lock()
///解锁
func unlock()
///挂起当前线程,直到收到signal
open func wait()
///在指定时间内,挂起当前线程,直到收到signal
open func wait(until limit: Date) -> Bool
///发送条件信号,唤醒一个(优先级最高或者等待时间最长的)等待线程
open func signal()
///广播条件信号,唤醒所有等待线程
open func broadcast()

使用也非常简单。

///swift
var i = 0;
public func test_condition() {
    NSLog("start")
    self.condition = NSCondition()
    DispatchQueue.global(qos: .default).async {
        self.condition?.lock()
        while(self.i == 0) {
            self.condition?.wait()
        }
        NSLog("1:"+Thread.current.description);
        self.condition?.unlock()
    }
    DispatchQueue.global(qos: .default).async {
        self.condition?.lock()
        while(self.i == 0) {
            self.condition?.wait()
        }
        NSLog("2:"+Thread.current.description);
        self.condition?.unlock()
    }
    DispatchQueue.global(qos: .default).async {
        self.condition?.lock()
        sleep(3)
        self.i += 1
        //唤醒线程
        self.condition?.signal()
//            self.condition?.broadcast()
        NSLog("3:"+Thread.current.description);
        self.condition?.unlock()
    }
    NSLog("end")
}

use mutex and cond

接下来我们来看看NSCondition底层实现,在Swift Foundation源码 NSLock.swift文件中,找到以下代码(提取了部分关键代码):

private typealias _MutexPointer = UnsafeMutablePointer<pthread_mutex_t>
private typealias _ConditionVariablePointer = UnsafeMutablePointer<pthread_cond_t>

open class NSCondition: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
    internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
    public override init() {
         //初始化mutex、cond
        pthread_mutex_init(mutex, nil)
        pthread_cond_init(cond, nil)
    }
    open func lock() {
         //使用mutex加锁
        pthread_mutex_lock(mutex)
    }
    open func unlock() {
         //使用mutex解锁
        pthread_mutex_unlock(mutex)
    }
    open func wait() {
         //挂起当前线程
        pthread_cond_wait(cond, mutex)
    }
    open func wait(until limit: Date) -> Bool {
        guard var timeout = timeSpecFrom(date: limit) else {
            return false
        }
        return pthread_cond_timedwait(cond, mutex, &timeout) == 0
    }
    open func signal() {
         //发送条件信号,唤醒线程
        pthread_cond_signal(cond)
    }
    open func broadcast() {
         //广播条件信号,唤醒所有线程
        pthread_cond_broadcast(cond)
    }
}

NSConditionLock

NSConditionLockFoundation框架提供的条件锁,可以确保线程仅在满足特定条件时才能获取锁。
NSConditionLock其实是对NSCondition条件变量的封装,内置了Int类型的cond条件。相比来说还是NSCondition可定制自由度高一点。
我们先来看常用API:

///加锁
func lock()
///解锁
func unlock()
///获取锁,条件变量等于指定的值,才能获取锁,一直等待
open func lock(whenCondition condition: Int)
///尝试加锁,返回是否成功,不阻塞
open func `try`() -> Bool
///如果条件变量等于某个值,尝试加锁,返回是否成功
open func tryLock(whenCondition condition: Int) -> Bool
///解锁,并设置条件变量的值
open func unlock(withCondition condition: Int)
///在指定时间内一直尝试加锁,返回是否成功
open func lock(before limit: Date) -> Bool
///尝试在指定的时间之前获取锁,此方法将阻止线程的执行,直到可以获取锁或达到限制为止。
open func lock(whenCondition condition: Int, before limit: Date) -> Bool

使用也很简单。

//swift
public func test_conditionlock() {
    NSLog("start")
    //初始化锁,设置 cond = 3
    self.conditionLock = NSConditionLock(condition: 3)
    DispatchQueue.global(qos: .default).async {
        //获取锁,等待 cond == 1
        self.conditionLock?.lock(whenCondition: 1)
        NSLog("1:"+Thread.current.description);
        self.conditionLock?.unlock(withCondition: 2)
    }
    DispatchQueue.global(qos: .default).async {
        //获取锁,等待 cond == 4,最多等待10秒
        self.conditionLock?.lock(whenCondition: 4, before: Date().advanced(by: 15))
        NSLog("2:"+Thread.current.description);
        self.conditionLock?.unlock(withCondition: 2)
    }
    DispatchQueue.global(qos: .default).async {
        //获取锁,等待 cond == 3
        self.conditionLock?.lock(whenCondition:3)
        sleep(5)
        NSLog("3:"+Thread.current.description);
        self.conditionLock?.unlock(withCondition: 1)
    }
    NSLog("end")
}

use NSCondition

接下来我们来看看NSConditionLock底层实现,在Swift Foundation源码 NSLock.swift文件中,找到以下代码(提取了部分关键代码):

open class NSConditionLock : NSObject, NSLocking {
    internal var _cond = NSCondition()
    internal var _value: Int
    internal var _thread: _swift_CFThreadRef?
    public init(condition: Int) {
         //初始化条件变量
        _value = condition
    }
    open func lock() {
         //加锁
        let _ = lock(before: Date.distantFuture)
    }
    open func unlock() {
        //解锁
        _cond.lock()
        _thread = nil
        _cond.broadcast()
        _cond.unlock()
    }
    open func lock(whenCondition condition: Int) {
        let _ = lock(whenCondition: condition, before: Date.distantFuture)
    }
    open func `try`() -> Bool {
        return lock(before: Date.distantPast)
    }
    open func tryLock(whenCondition condition: Int) -> Bool {
        return lock(whenCondition: condition, before: Date.distantPast)
    }
    open func unlock(withCondition condition: Int) {
         //解锁,设置条件变量
        _cond.lock()
        _thread = nil
        _value = condition
        _cond.broadcast()
        _cond.unlock()
    }
    open func lock(before limit: Date) -> Bool {
         //加锁,首次直接unlock,其他case需要等待_cond.signal()
        _cond.lock()
        while _thread != nil {
            if !_cond.wait(until: limit) {
                _cond.unlock()
                return false
            }
        }
        _thread = pthread_self()
        _cond.unlock()
        return true
    }
    open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
         //加锁,不是首次或者条件变量不匹配,等待_cond.signal()
        _cond.lock()
        while _thread != nil || _value != condition {
            if !_cond.wait(until: limit) {
                _cond.unlock()
                return false
            }
        }
        _thread = pthread_self()
        _cond.unlock()
        return true
    }
}

NSRecursiveLock

NSRecursiveLockFoundation框架提供的递归锁。作用是,一个锁可以由同一线程多次获取而不会导致死锁,和synchronized(objc_sync)一样,底层也是对pthread_mutex recursive的封装。
我们先来看下API:

///加锁
func lock()
///解锁
func unlock()
///尝试加锁,返回是否成功,不阻塞
open func `try`() -> Bool
///在指定时间内一直尝试加锁,返回是否成功
open func lock(before limit: Date) -> Bool

使用起来也很简单:

//swift
public func test_recursivelock() {
    NSLog("start")
    self.recursiveLock = NSRecursiveLock()
    DispatchQueue.global(qos: .default).async {
        self.recursiveLock?.lock()
        sleep(3)
        //重复获取锁
        self.recursiveLock?.lock()
        NSLog("1:"+Thread.current.description);
        self.recursiveLock?.unlock()
        self.recursiveLock?.unlock()
    }
    DispatchQueue.global(qos: .default).async {
        self.recursiveLock?.lock()
        sleep(3)
        NSLog("2:"+Thread.current.description);
        self.recursiveLock?.unlock()
    }
    NSLog("end")
}

use recursive mutex

接下来我们来看看NSRecursiveLock底层实现,在Swift Foundation源码 NSLock.swift文件中,找到以下代码(提取了部分关键代码):

//swift
private typealias _MutexPointer = UnsafeMutablePointer<pthread_mutex_t>
private typealias _RecursiveMutexPointer = UnsafeMutablePointer<pthread_mutex_t>
private typealias _ConditionVariablePointer = UnsafeMutablePointer<pthread_cond_t>

open class NSRecursiveLock: NSObject, NSLocking {
    //初始化 pthread_mutex_t 锁
    internal var mutex = _RecursiveMutexPointer.allocate(capacity: 1)
    private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
    private var timeoutMutex = _MutexPointer.allocate(capacity: 1)

    public override init() {
        super.init()
         //初始化 pthread_mutexattr_t 互斥属性,设置递归类型 PTHREAD_MUTEX_RECURSIVE
        var attrib = pthread_mutexattr_t()
        withUnsafeMutablePointer(to: &attrib) { attrs in
            pthread_mutexattr_init(attrs)
            pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
            pthread_mutex_init(mutex, attrs)
        }
        pthread_cond_init(timeoutCond, nil)
        pthread_mutex_init(timeoutMutex, nil)
    }
    open func lock() {
         //使用mutex加锁
        pthread_mutex_lock(mutex)
    }
    open func unlock() {
         //使用mutex解锁
        pthread_mutex_unlock(mutex)=
        // Wakeup any threads waiting in lock(before:)
        pthread_mutex_lock(timeoutMutex)
        pthread_cond_broadcast(timeoutCond)
        pthread_mutex_unlock(timeoutMutex)
    }
    open func `try`() -> Bool {
        return pthread_mutex_trylock(mutex) == 0
    }
    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)
        guard var endTime = timeSpecFrom(date: limit) else {
            return false
        }
        return pthread_mutex_timedlock(mutex, &endTime) == 0
    }
}

The Next

  1. iOS 中常用的锁
  2. iOS OSSpinLock
  3. iOS Synchronized
  4. iOS NSLock那些琐
  5. iOS Atomic 底层分析

参考链接

swift foundation
opensource objc4

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章