同步与锁

244 阅读4分钟

避免多个线程同时读写一个数据,产生不可预料的后果,需要将多个线程对一个数据的访问同步。因此,对数据的访问被原子化了。

  • 同步:一个线程访问数据未结束时,其他线程不得访问
  • 锁的作用:保持多线程在并发时的数据(全局变量和堆数据)一致性。
  • 原子操作:单指令的操作(单指令的执行是不会被打断的)

同步的常见方法是使用锁。即,每个线程在访问数据/资源之前,先试图获取锁,访问结束后释放锁。如果锁已经被占用,线程需等待知道锁被释放。

信号量

信号量分二元信号量和多元信号量。

二元信号是一种最简单的锁。只有两种状态:占用和非占用。 适用于只能被唯一一个线程独占访问的资源。 当一二元信号量处于非占用时,第一个试图获取二元二元信号量的线程会获得锁,其他试图获取的线程只能等待。

多元信号量(简称信号量)允许多个线程并发访问资源。一个初始值为N的信号量,允许N个线程并发访问。 线程访问资源的时候首先获取信号量,进行如下操作:

  • 将信号量的值减1
  • 判断值是否小于0,是就进入等待状态,否则继续执行

在访问结束以后,线程释放信号量,进行如下操作:

  • 将信号量的值加1
  • 如果信号量的值小于1,唤醒一个等待中的线程???
互斥量

和二元信号量类似,资源同时仅允许一个线程单独访问。

区别是信号量可以在整个系统被任意线程获取和释放。互斥量则是哪个线程获取的就要哪个线程释放(解铃还须系铃人),其他线程去帮忙释放是无效的。

临界区

临界区的锁的获取称为进入临界区,锁的释放称为离开临界区。临界区是一种比互斥量更加严格的同步手段。

互斥量和信号量在系统内任何进程都是可见的,即,A进程获取B进程创建的的互斥量和信号量是合法的。而临界区的作用范围限制在一个进程中,其中的锁对其他进程是不可见的。

读写锁

是一个钟致力于更加更定场合的同步锁。假设写入操作不是原子型就必须使用同步手段避免出错。上面介绍的信号量、互斥量和临界区都可以保证顺序正确,但是存在性能问题。例如:对于频繁读、偶尔写入的情况会很低效。读写锁可以满足这种需求。

读写锁的获取方式有两种:共享的或者独占的。当锁处于自由状态,两种获取方式都能成功。当处于共享状态时,以共享的方式获取仍能够成功,此时,锁分配给了多个线程。然而,以独占的方式获取共享状态的锁,必须等待所有线程释放了这个锁。相应的,处于独占状态的锁将阻止其他线程以任何方式的获取。

条件变量

作用类似于栅栏,也是一种同步手段。 对于条件变量,线程有两种操作:等待和唤醒。

  • 等待:一个条件变量可以同时被多个线程等待。
  • 唤醒:线程可以唤醒条件变量,同时,其他等待这个条件变量的线程都会被唤醒。

即,使用条件变量可以让多个线程一起等待某个事件的发生,当事件发生时(条件变量被唤醒),所有线程一起恢复执行。例如,两个线程分别下载A、B两张图片,当图片下载完成后,把AB成和一张图。

参考

  • 《程序员的自我修养》