我喜欢将监视器视为具有私有互斥锁的对象,并且每个过程都受到该互斥锁的保护。所以像:
MyClass {
// private shared variables
...
mutex m;
// public procedures
Procedure P1(...) {
acquire(m);
...
release(m);
}
Procedure P2(...) {
acquire(m);
...
release(m);
}
initialisation code (...) { ... }
}
互斥锁保证一次只有一个线程/进程可以操作对象,因为每个公共过程都包含互斥锁获取/释放。
现在那些条件变量呢?嗯,通常你不需要它们。但有时你有一些条件,一个线程需要等待另一个线程,这就是条件变量的来源。
例如,假设您正在实现一个并发队列对象。你会有一个push()
程序和一个pop()
程序。但是如果队列是空的怎么办?
由于代码如下:
Thread A Thread B
if (not q.empty())
if (not q.empty())
then q.pop() // now q is empty!
then q.pop() // error!
因此,您必须定义一个 pop() 过程,该过程在队列为空时返回一个特殊值,然后旋转等待队列变为非空。但是旋转等待是非常低效的。
因此,您改为使用条件变量。条件变量有两种方法:wait()
和notify()
。Wait() 原子地放弃你持有的互斥锁,然后阻塞线程,直到其他线程调用 notify()。所以像这样:
condition_variable cv;
Procedure int pop() {
acquire(m);
while (q.empty()) {
wait(cv, m) // atomically release m and wait for a notify() on cv.
// when wait() returns it automatically reacquires mutex m for us.
}
rval = q.pop();
release(m);
return rval;
}
Procedure push(int x) {
acquire(m);
q.push(x);
notify(cv); // wake up everyone waiting on cv
release(m);
}
现在您有了一个数据结构/对象,其中一次只能有一个线程访问该对象,并且您可以处理不同线程之间的操作需要以特定顺序发生的情况(例如至少一个 push()必须在每个 pop() 完成之前发生。)