iOS-26.锁的原理

1,108 阅读3分钟

ios底层文章汇总

1 多线程的资源抢夺问题

案例:买票 两个端口同时进行买票,第一个端口买票是总票数是1000张,没等第一个端口买票完成,第二个端口也从总票数为1000时进行买票,返回时均为1000-1,造成了数据错误

image.png

2 解决资源抢夺问题:加锁实现多读单写

当多个线程资源抢夺和数据安全问题的两种解决方案:

  • 互斥锁(即同步锁):@synchronized
  • 自旋锁

2.1 互斥锁

  • 保护临界区,确保同一时间,只有一条线程能够执行
  • 只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个锁对象 @synchronized(self)
  • 加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠

互斥锁特点

  • 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差
  • 能够加锁的任意 NSObject 对象
  • 锁对象一定要保证所有的线程都能够访问

2.2 自旋锁

  • 自旋锁与互斥锁类似,但它不是通过休眠使线程阻塞,而是忙等(即原地打转,称为自旋)阻塞状态
  • 使用场景:锁持有的时间短,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符atomic,本身就有一把自旋锁
  • 加入了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方法,一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能

2.3 自旋锁与互斥锁的比较

  • 相同点:在同一时间,保证了只有一条线程执行任务,即同步

  • 不同点:

    • 互斥锁:发现其他线程执行,当前线程 休眠(即就绪状态),进入等待执行,即挂起。一直等其他线程打开之后,然后唤醒执行
    • 自旋锁:发现其他线程执行,当前线程 一直询问,处于忙等状态耗费的性能比较高
  • 场景:根据任务复杂度区分,使用不同的锁,但判断不全时,更多是使用互斥锁去处理

    • 当前的任务状态比较短小精悍时,用自旋锁
    • 反之的,用互斥锁

2.4 atomic 原子锁 & nonatomic 非原子锁

atomic 和 nonatomic主要用于属性的修饰,以下是相关的一些说明:

  • atomic是原子属性,是为多线程开发准备的,是默认属性!

    • 在属性的 setter 方法中,增加了锁(自旋锁),保证同一时间,只有一条线程对属性进行操作
    • 同一时间 单(线程)写多(线程)读线程处理技术
    • Mac开发中常用
  • nonatomic 是非原子属性

    • 没有锁,性能高
    • 移动端开发常用

atomic与nonatomic 的区别

  • nonatomic

    • 非原子属性
    • 非线程安全适合内存小的移动设备
  • atomic

    • 原子属性(线程安全),针对多线程设计的,默认值
    • 保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以读值)
    • atomic 本身就有一把锁(自旋锁单写多读
    • 线程安全,需要消耗大量的资源

iOS 开发的建议

  • 所有属性都声明为 nonatomic
  • 尽量避免多线程抢夺同一块资源 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力