iOS Lock 锁

发布于 2024-09-02 22:47:47 字数 11551 浏览 23 评论 0

iOS 中常见的 锁

开发当中我们经常会使用到锁来控制,线程安全问题。

  • semaphore: 信号量 (当限制并发线程=1 时,可以当作锁来使用)
  • nslock: 互斥锁
  • conditionLock: 条件锁
  • recursiveLock: 递归锁
  • pthread_mutex: 互斥锁
  • os_unfair_lock: 自旋锁
  • condition: 条件锁
  • barrier: 栅栏函数

使用说明

import UIKit

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    semaphore()
    nslock()
    conditionLock()
    recursiveLock()
    pthread_mutex()
    os_unfair_lock()
    condition1()
    condition2()
    condition3()
    barrier()
  }

  // 互斥锁
  func nslock() {
    // 创建锁
    let locker = NSLock()
    // 主线程队列添加异步任务,其实就是不阻塞当前上下文,将任务添加到队列的最后面执行。
    DispatchQueue.main.async {
      print("NSLOCK: 我一定先执行,因为我在的线程已经上锁,其他线程加锁内容不能比插进来。")
      locker.unlock()
    }

    DispatchQueue(label: "label").async { // 子线程队列
      print("NSLOCK: 我是不确定的,因为我是子线程,什么时候执行我全看苹果 GCD 框架如何安排,有可能早于主线程内容输出哦!")
      // 不论这个子线程是否早于主线程执行,都会在主线程 do something1 之后执行。 因为主线程已经先上锁了。 所以必须等主解锁才可以进来。
      locker.lock()
      print("NSLOCK: 即使我先执行也没卵用,因为主线程已经上锁了,我肯定最后被执行了")
      locker.unlock()
    }

    locker.lock()

    /**
     Summary

     Attempts to acquire a lock and immediately returns a Boolean value that indicates whether the attempt was successful.
     Declaration

     func `try`() -> Bool
     Returns

     true if the lock was acquired, otherwise false.

     尝试去加锁,如果加上了就返回 true,没加上就返回 false,在这里返回了 false,因为在当前线程之前已经 lock 了,必须 unlock 过后才可以再次 lock。
     */
    locker.try()
    print("NSLOCK: 主线程已经上锁")
  }

  // 信号量
  func semaphore() {
    let semaphore = DispatchSemaphore(value: 0)

//    DispatchQueue.main.async { // 主线程队列 如果在主队列上异步执行 也会被卡住
//      print("do sth")
//      semaphore.signal()
//    }

    DispatchQueue(label: "label").async { // 子线程队列
      print("DispatchSemaphore: 我依然会执行,因为信号量现在卡住的是主线程!")
      semaphore.signal()
      print("DispatchSemaphore: 上面的 semaphore.signal 代码会解开信号量,使主线程继续 继续往下跑!")
    }

    // 执行 wait 会对信号量总数 -1,这时如果信号量 小于 0 则阻塞当前线程,否则继续。
    semaphore.wait()
    print("DispatchSemaphore: 子线程执行完 semaphore.signal() 才会让我执行,否则一直卡住了。")
  }

  // 条件锁
  func conditionLock() {

    // 初始化条件锁 (当然你也可以给设置初始 Condition(Int)) 默认不设置 Condition = 0
    let locker = NSConditionLock()
    // let locker = NSConditionLock.init(condition: 2)

    /**
     说明: locker 有一个核心属性是 Condition
     locker.lock() 当没有被轮到执行的时候会卡住当前线程 并不设置 locker 的 condition 属性
     locker.lock(whenCondition: A) 当没有被轮到执行的时候会卡住当前线程,并且当 locker 的 condition=A 的时候才执行
     locker.unlock() 解除卡住当前线程的 locker 并不设置 locker 的 condition 属性
     locker.unlock(withCondition: B) 解除卡住当前线程的 locker 并设置 locker 的 condition 属性=B

     这里补充一下:
     有一种情况,NSConditionLock 初始 condition 设置成 2
     同时有 2 个 lock 代码
     1. locker.lock()
     2. locker.lock(whenCondition: 2)
     会执行 1 locker.lock(),只有没有 1 locker.lock() 的情况下,才会执行 2。 也就是说 locker.lock() 优先级最大。
    */

    /**
     实验步骤:
     由于线程 1,2,3 都加了锁卡住了线程执行,由于有 locker.lock(),所以会优先执行 queue1 中的代码,
     当执行完成将 condition 设置成 1,这个时候 queue2 看到 condition 设置成 1,它可以执行了,以此类推
     所以试验结果是 queue1 queue2 queue3 顺序执行
     */

    // 线程一
    let queue1 = DispatchQueue.init(label: "thread1")
    queue1.async {
      locker.lock()
      print("NSConditionLock1:\(Thread.current)")
      locker.unlock(withCondition: 1) //
      // locker.unlock() // 如果 unlock 没有指定 condition,那么其他线程 lock 指定 condition 的代码将被锁住不会执行。
    }

    // 线程二
    let queue2 = DispatchQueue.init(label: "thread2")
    queue2.async {
      locker.lock(whenCondition: 1)
      print("NSConditionLock2:\(Thread.current)")
      locker.unlock(withCondition: 2)
    }

    // 线程三
    let queue3 = DispatchQueue.init(label: "thread3")
    queue3.async {
      locker.lock(whenCondition: 2)
      print("NSConditionLock3:\(Thread.current)")

      // 设置回初始值 0,当然你也可以设置成你想用的值,这里根据实际情况来,因为我整个流程结束了,所以我把这个对象的属性设置成默认值。
      locker.unlock(withCondition: 0)
    }

  }

  // 递归锁
  func recursiveLock() {

    // 创建锁
    let locker = NSRecursiveLock()

    /**
     说明: 递归锁不像互斥锁那样,在同一个线程当中,如果两次 lock,前一个没有 unlock 会阻塞后面的代码执行 例如:

     --- 互斥锁 NSLOCK ---
     locker.lock() // 锁住
     locker.lock() // 发现上一个锁没有解锁 就卡住下面的代码
     print("thread1")
     locker.unlock()

     --- 递归锁 NSRecursiveLock ---
     locker.lock() // 锁住
     locker.lock() // 发现上一个锁没有解锁,没事继续执行,locker 会记录 已经加锁 2 次了
     print("thread1") // 打印
     locker.unlock() // 解锁 1 次,因为之前上锁 2 次,现在只解锁 1 次肯定不行,其他线程就无法加锁,就被卡住了。

     let queue = DispatchQueue(label: "thread2")
     queue.async {
       locker.lock() // 被卡住 因为递归锁 还有 1 次 unlock 才可以释放掉上一个线程
       print("thread2")
       locker.unlock()
     }

     */

    let queue1 = DispatchQueue(label: "thread1")
    queue1.async {
      locker.lock()
      locker.lock()
      print("NSRecursiveLock: 会被执行")
      locker.unlock()
    }

    let queue = DispatchQueue(label: "thread2")
    queue.async {
      locker.lock()
      print("NSRecursiveLock: 不会被执行,要上一个线程 unlock 次数 = lock 次数 才可以")
      locker.unlock()
    }
  }

  // 互斥锁 c 语言实现的跨平台互斥锁 (猜测 NSLOCK 是封装的 pthread_mutex_t,仅仅是猜测)
  func pthread_mutex() {

    // 创建锁
    var mutex = pthread_mutex_t()
    pthread_mutex_init(μtex, nil)

    DispatchQueue.main.async {
      print("NSLOCK: 我一定先执行,因为我在的线程已经上锁,其他线程加锁内容不能比插进来。")
      pthread_mutex_unlock(μtex)
    }

    DispatchQueue(label: "label").async { // 子线程队列
      print("NSLOCK: 我是不确定的,因为我是子线程,什么时候执行我全看苹果 GCD 框架如何安排,有可能早于主线程内容输出哦!")
      // 不论这个子线程是否早于主线程执行,都会在主线程 do something1 之后执行。 因为主线程已经先上锁了。 所以必须等主解锁才可以进来。
      pthread_mutex_lock(μtex)
      print("NSLOCK: 即使我先执行也没卵用,因为主线程已经上锁了,我肯定最后被执行了")
      pthread_mutex_unlock(μtex)
    }
    pthread_mutex_lock(μtex)
    print("NSLOCK: 主线程已经上锁")
  }

  // 自旋锁 由于 OSSpinLock 已经被废弃掉了,原因是低优先级的线程如果锁住了资源,高优先级的线程访问时会破坏掉锁的资源.
  // 苹果开发 os_unfair_lock 是替代 OSSpinLock 的产物
  // 自旋锁是目前来看这些锁中性能最好的一个
  func os_unfair_lock() {
    var unfairLock = os_unfair_lock_s()

    // 主线程队列添加异步任务,其实就是不阻塞当前上下文,将任务添加到队列的最后面执行。
    DispatchQueue.main.async {
      print("NSLOCK: 我一定先执行,因为我在的线程已经上锁,其他线程加锁内容不能比插进来。")
      os_unfair_lock_unlock(&unfairLock)
    }

    DispatchQueue(label: "label").async { // 子线程队列
      print("NSLOCK: 我是不确定的,因为我是子线程,什么时候执行我全看苹果 GCD 框架如何安排,有可能早于主线程内容输出哦!")
      // 不论这个子线程是否早于主线程执行,都会在主线程 do something1 之后执行。 因为主线程已经先上锁了。 所以必须等主解锁才可以进来。
      os_unfair_lock_lock(&unfairLock)
      print("NSLOCK: 即使我先执行也没卵用,因为主线程已经上锁了,我肯定最后被执行了")
      os_unfair_lock_unlock(&unfairLock)
    }
    os_unfair_lock_lock(&unfairLock)
    print("NSLOCK: 主线程已经上锁")
  }

  // 条件锁 场景一
  func condition1() {
    let locker = NSCondition()

    /** --- 场景一 当互斥锁使用 --- */
    // 主线程队列添加异步任务,其实就是不阻塞当前上下文,将任务添加到队列的最后面执行。
    DispatchQueue.main.async {
      print("NSLOCK: 我一定先执行,因为我在的线程已经上锁,其他线程加锁内容不能比插进来。")
      locker.unlock()
    }

    DispatchQueue(label: "label").async { // 子线程队列
      print("NSLOCK: 我是不确定的,因为我是子线程,什么时候执行我全看苹果 GCD 框架如何安排,有可能早于主线程内容输出哦!")
      // 不论这个子线程是否早于主线程执行,都会在主线程 do something1 之后执行。 因为主线程已经先上锁了。 所以必须等主解锁才可以进来。
      locker.lock()
      print("NSLOCK: 即使我先执行也没卵用,因为主线程已经上锁了,我肯定最后被执行了")
      locker.unlock()
    }
    locker.lock()
    print("NSLOCK: 主线程已经上锁")
  }

  // 条件锁 场景二
  func condition2() {
    let locker = NSCondition()

    /** --- 场景二 阻塞单个上下文线程 --- */
    let queue = DispatchQueue.init(label: "线程 1")
    queue.async {
      locker.lock()
      print("NSCondition: 我是 A,先执行")
      // 卡住当前线程 直到 2s 后 自动解开
//      locker.wait(until: Date.init(timeInterval: 2, since: Date()))
      // 一直卡住 直到 locker.broadcast() locker.signal() 才会解开

      /**
       Summary
       Blocks the current thread until the condition is signaled.
       You must lock the receiver prior to calling this method.

       Wait 这个方法,功能是阻塞当前线程,直到被通知可以解开。
       注意的是: 调用这个方法的时候只能在 Lock 的上下文中,也就是只能在 lock 和 unlock 之前调用。
       */
      locker.wait()
      print("NSCondition: 我要等 A 执行之后才执行,因为线程被卡住了")
      locker.unlock()
    }

    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {

      /**
       Summary
       Signals the condition, waking up one thread waiting on it.
       Discussion
       You use this method to wake up one thread that is waiting on the condition. You may call this method multiple times to wake up multiple threads. If no threads are waiting on the condition, this method does nothing.
       To avoid race conditions, you should invoke this method only while the receiver is locked.

       发送一个信号,去解开 wait 方法阻塞的线程,这个方法一次只能解开一个,如果有多个线程被 wait 你可以调用多次去解开
       为了避免条件竞争,你应该调用这个方法只有当 wait 的线程在 lock 和 unlock 之间 才可以。
       */
      locker.signal()
      /**
       这个我闲麻烦就不贴文档了,就是一次解开所有 wait 的线程,并且解开的 wait 线程,wait 必须是在 lock 和 unlock 之中调用的。
       */
//      locker.broadcast()
    }
  }

  // 条件锁 场景三
  func condition3() {
    let locker = NSCondition()

    /** --- 场景三 阻塞多个上下文线程 --- */
    let queue = DispatchQueue(label: "线程 1")
    queue.async {
      locker.lock()
      print("NSCondition: 我是 A,先执行")
      // 卡住当前线程 直到 2s 后 自动解开
      //      locker.wait(until: Date.init(timeInterval: 2, since: Date()))
      // 一直卡住 直到 locker.broadcast() locker.signal() 才会解开


      /**
       Summary
       Blocks the current thread until the condition is signaled.
       You must lock the receiver prior to calling this method.

       Wait 这个方法,功能是阻塞当前线程,直到被通知可以解开。
       注意的是: 调用这个方法的时候只能在 Lock 的上下文中,也就是只能在 lock 和 unlock 之前调用。
       */
      locker.wait()
      print("NSCondition: 我要等 A 执行之后才执行,因为线程被卡住了")
      locker.unlock()
    }

    /** --- 场景三 阻塞多个上下文线程 --- */
    let queue2 = DispatchQueue(label: "线程 2")
    queue2.async {
      locker.lock()
      print("NSCondition: 我是 B,先执行")
      // 卡住当前线程 直到 2s 后 自动解开
      //      locker.wait(until: Date.init(timeInterval: 2, since: Date()))
      // 一直卡住 直到 locker.broadcast() locker.signal() 才会解开

      /**
       Summary
       Blocks the current thread until the condition is signaled.
       You must lock the receiver prior to calling this method.

       Wait 这个方法,功能是阻塞当前线程,直到被通知可以解开。
       注意的是: 调用这个方法的时候只能在 Lock 的上下文中,也就是只能在 lock 和 unlock 之前调用。
       */
      locker.wait()
      print("NSCondition: 我要等 B 执行之后才执行,因为线程被卡住了")
      locker.unlock()
    }

    DispatchQueue.main.asyncAfter(deadline: .now() + 2) { // 延迟 2s
      /**
       这个我闲麻烦就不贴文档了,就是一次解开所有 wait 的线程,并且解开的 wait 线程,wait 必须是在 lock 和 unlock 之中调用的。
       */
      locker.broadcast()
      // 当然你也可以这么写
      // locker.signal()
      // locker.signal()
    }

  }

  // 栅栏函数
  func barrier() {

    // 创建并发 Queue,并发 Queue 下执行的 Async 操作会自动有 GCD 派发多个不同的线程去执行
    let queue = DispatchQueue.init(label: "concurrent", attributes: .concurrent)

    queue.async {
      print("我是 A:\(Thread.current)")
    }
    queue.async {
      print("我是 B:\(Thread.current)")
    }

    /**
     barrier 会将并发队列中的任务隔离开 Run 一下看下输出就知道了。
     */
    queue.async(flags: .barrier) {
      print("------------隔离线------------")
      print(Thread.current)
      print("------------AB 在一起,CD 在一起------------")
    }

    queue.async {
      print("我是 C:\(Thread.current)")
    }
    queue.async {
      print("我是 D:\(Thread.current)")
    }
  }

}

总结

看完上面的 锁 再问你如何把任务在各个线程中按照一定顺序执行你会说有多少种。

  1. GCD Group
  2. NSOperation
  3. GCD Barrier
  4. 串行任务队列 一个一个顺序执行
  5. NSLockCondition 条件锁
  6. NSCondition 条件锁

等等 是不是太多,下回在遇到多线程中处理任务就不会慌张了。

Demo: https://github.com/zColdWater/locker

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

天涯沦落人

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

新人笑

文章 0 评论 0

mb_vYjKhcd3

文章 0 评论 0

小高

文章 0 评论 0

来日方长

文章 0 评论 0

哄哄

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文