线程安全和线程同步 | 青训营笔记

114 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第10天

原子操作、临界区、线程同步

单个机器指令执行的操作称为原子操作

高级程序语言写出的一条语句,编译出来的往往是多条机器指令,此时称这条语句对应的操作不是原子的。如果多个线程对一个共享数据同时进行读写,会导致意想不到的后果。

临界区指的是不能被并发执行的一段代码(共享数据、代码块)。

线程同步指的是在一个线程访问临界区的时候,其他线程不能对临界区进行访问,即对临界区的访问变成原子化了。

互斥量(Mutex) 是最简单的一种锁,在锁已经被占用的时候试图获取锁时,线程会等待,直到锁重新可用。由哪个线程获取就由哪个线程释放,常用于临界区的互斥访问。

信号量(Semaphore) 允许多个线程并发访问资源,可以实现线程同步或者控制并发访问的数量。

线程访问资源首先获取信号量:

  • 信号量的值 -1
  • 如果信号量的值小于 0,则线程进入等待状态,否则继续执行

访问完资源后线程释放信号量:

  • 将信号量的值 +1
  • 如果信号量的值大于 0,唤醒等待中的线程

死锁

死锁是指多个线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁

死锁产生的四个必要条件

  • 互斥条件:资源访问是互斥的
  • 占有且等待条件:线程持有了资源不释放,同时请求其他资源
  • 不可抢占条件:其它线程不能强制夺取资源,只能由占有资源的线程主动释放
  • 循环等待条件:线程等待形成了环路

破坏产生死锁的四个必要条件之一可以预防死锁的发生

atomic、nonatomic

Objective-C 中定义一个类的属性时,可以指定该属性的原子性(atomicnonatomic),默认是 atomic

atomic

  • 对属性 getter、setter 调用是线程安全的
  • 需要耗费资源为属性加锁

nonatomic

  • 访问不是线程安全的
  • 访问效率比 atomic 更高

dispatch_barrier_async

考虑多线程进行数据读写的场景,多个写操作不能并发执行,写操作和读操作不能并发执行,但是多个读操作是可以并发执行的,此时可以使用 dispatch_barrier_async 同时满足提高读操作效率和保证线程安全的要求。dispatch_barrier_async 函数会等队列中的全部任务执行结束后,再将指定的任务 X 追加到队列,之后提交的任务也需要等待 X 执行结束,仿佛 dispatch_barrier_async给队列添加了一道“栅栏”

Dispatch Semaphore

Dispatch Semaphore 是 GCD 提供的信号量接口,Dispatch Semaphore 可以实现成二元信号量或者多元信号量,达到线程同步、控制并发处理数量的效果