操作系统线程之间的通信是一个很重要的问题,在用 C 语言在 Linux 实现经典的“生产者和消费者”以及“读者写者”问题时产生了一些疑惑,记录之。
Problem
进程间通信的需要解决的核心问题就是线程之间的同步,比如说,有一个程序,两个线程同时对一个共享变量 cnt
分别进行 n
次加法操作,如果其初始值为 0 的话,那么正确的运行结果应该是 cnt = 2n
,但如果我们在一台单 CPU 的机器上运行的话,结果往往不是 2n
。为什么呢?因为机器可能不会按照我们思考的方式去执行这样的指令,一种很容易想到的产生错误的方式就是:一个线程修改了值(对 cnt
进行加 1 )之后还没写回去,这个时候另一个线程就读到了 cnt
的旧值并且在旧值之上进行了更新操作,二者都写回去的时候,cnt
的值只加一而不是加二。一个显而易见的解决方式就是将各自的操作都变成原子化,也即一个线程带来的任何改变都能被另一个线程所感知到,不过这样带来的 overhead(额外开销)过大,对性能是个很大的影响。因此伟大的计算机科学家们提出了两个概念用于解决这个问题,分别是 mutex(互斥量)和 semphore(信号量):
Strictly speaking, a mutex is a locking mechanism used to synchronize access to a resource. Only one task (can be a thread or process based on OS abstraction) can acquire the mutex. It means there will be ownership associated with mutex, and only the owner can release the lock (mutex).
Semaphore is signaling mechanism (“I am done, you can carry on” kind of signal). For example, if you are listening songs (assume it as one task) on your mobile and at the same time your friend called you, an interrupt will be triggered upon which an interrupt service routine (ISR) will signal the call processing task to wakeup.
互斥量是一把锁,用于控制对资源的访问,同一时间只有一个线程能够获得这把锁,也只有获得锁的线程才能够释放这把锁;信号量是一个信号,用于线程之间的通讯。特别地,信号量 s 有两种操作:
- P(s):如果 s 非零,则将 s -1 并返回;如果 s 为 0,则挂起该线程,直到 s 非 0,一般来说,是等待 V 操作的通知。
- V(s):V 操作将 s + 1。如果有任何线程阻塞在 P 操作等待,则 V 操作会通知折线线程中的一个,让他苏醒完成 s - 1 操作。
这里几点需要注意:
- P 和 V 操作都是原子化的,也就是说检测信号值、执行操作、写回信号值这三个子操作时不可能被打断的。也只有这样,P、V 操作才有意义。
- V 操作唤醒等待线程时是随机的,我们无法预测哪个线程会苏醒,这里没有先来先走的事情存在
概念就先介绍到这里,可能还有一些不清楚的地方,就结合接下来的两个问题的代码实现,再进一步的讲解。
more >>