目录
为什么要引入管程
管程(Monitor)是一种操作系统中的同步机制,它的引入是为了解决多线程或多进程环境下的并发控制问题。
在传统的操作系统中,当多个进程或线程同时访问共享资源时,可能会导致数据的不一致性、竞态条件和死锁等问题。为了避免这些问题,需要引入一种同步机制来协调并发访问。
管程提供了一种高级的同步原语,它将共享资源和对资源的操作封装在一个单元中,并提供了对这个单元的访问控制机制。
相比于信号量机制,用管程编写程序更加简单,写代码更加轻松。
管程的定义和基本特征
1.管程的定义
"管程是一种机制,用于强制并发线程对一组共享变量的互斥访问(或等效操作)。此外,管程还提供了等待线程满足特定条件的机制,并通知其他线程该条件已满足的方法。"
这个定义描述了管程的两个主要功能:
- 互斥访问:管程确保多个线程对共享变量的访问互斥,即同一时间只有一个线程可以访问共享资源,以避免竞态条件和数据不一致性问题。
- 条件等待和通知:管程提供了等待线程满足特定条件的机制,线程可以通过条件变量等待某个条件满足后再继续执行,或者通过条件变量通知其他线程某个条件已经满足。
可以将管程理解为一个房间,这个房间里有一些共享的资源,比如变量、队列等。同时,房间里有一个门,只有一把钥匙。多个线程或进程需要访问房间内的资源时,它们需要先获得这把钥匙,一次只能有一个线程或进程持有钥匙,进入房间并访问资源。其他线程或进程必须等待,直到当前持有钥匙的线程或进程释放钥匙,才能获得钥匙进入房间。
此外,管程还提供了条件变量,类似于房间内的提示牌。线程在进入房间后,如果发现某个条件不满足(比如队列为空),它可以通过条件变量来知道自己需要等待,暂时离开房间,并将钥匙交给下一个等待的线程。当其他线程满足了等待的条件(比如向队列中添加了元素),它可以通过条件变量通知告诉正在等待的线程,使其重新获得钥匙进入房间,并继续执行。
2.管程的组成
管程由以下几个主要部分组成:
-
共享变量:管程中包含了共享的变量或数据结构,多个线程或进程需要通过管程来访问和修改这些共享资源。
-
互斥锁(Mutex):互斥锁是管程中的一个关键组成部分,用于确保在同一时间只有一个线程或进程可以进入管程。一旦一个线程或进程进入管程,其他线程或进程必须等待,直到当前线程或进程退出管程。
-
条件变量(Condition Variables):条件变量用于实现线程或进程之间的等待和通知机制。当一个线程或进程需要等待某个条件满足时(比如某个共享资源的状态),它可以通过条件变量进入等待状态。当其他线程或进程满足了这个条件时,它们可以通过条件变量发送信号来唤醒等待的线程或进程。
-
管程接口(对管程进行操作的函数):管程还包括了一组操作共享资源的接口或方法。这些接口定义了对共享资源的操作,并且在内部实现中包含了互斥锁和条件变量的管理逻辑。其他线程或进程通过调用这些接口来访问共享资源,从而确保了对共享资源的有序访问。
例如:
#include <iostream>
#include <mutex>
#include <condition_variable>
class Monitor {
private:
int count; // 共享变量
std::mutex mtx; // 互斥锁
std::condition_variable cond; // 条件变量
public:
Monitor() : count(0) {}
void enter() {
std::unique_lock<std::mutex> lock(mtx);
}
void exit() {
mtx.unlock();
}
void wait() {
count++;
cond.wait(lock);
count--;
}
void notify() {
if (count > 0) {
cond.notify_one();
}
}
void notifyAll() {
if (count > 0) {
cond.notify_all();
}
}
};
3.管程的基本特征
管程的基本特征包括:
-
互斥性(Mutual Exclusion):管程提供了互斥访问共享资源的机制,同一时间只允许一个线程或进程进入管程并执行操作,以避免数据竞争和冲突。
-
封装性(Encapsulation):管程将共享资源和对资源的操作封装在一起,对外部提供了一组抽象的接口或方法,使得其他线程或进程只能通过这些接口来访问和修改共享资源。
-
条件等待(Condition Wait):管程提供了条件变量,允许线程或进程在某个条件不满足时等待,并在条件满足时被唤醒继续执行。条件等待能够避免忙等待,提高系统的效率。
-
条件通知(Condition Signal):管程允许线程或进程在某个条件发生变化时发出通知,唤醒等待的线程或进程继续执行。条件通知使得线程或进程之间能够有效地进行协作和同步。
-
可阻塞性(Blocking):当一个线程或进程尝试进入管程时,如果管程已经被其他线程或进程占用,它将被阻塞,直到管程可用。同样,当一个线程或进程等待某个条件满足时,如果条件不满足,它也会被阻塞,直到条件满足。
-
公平性(Fairness):管程通常会提供公平性保证,即线程或进程按照它们等待的顺序获得对管程的访问权限。这样可以避免某些线程或进程一直被其他线程或进程抢占,导致饥饿现象。
这些特征使得管程成为一种强大的并发编程机制,可以简化并发程序的编写和调试过程,并提供了良好的线程或进程间的协作方式。
用管程解决生产者消费者问题
例子:
#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
class Monitor {
private:
std::queue<int> buffer; // 共享的缓冲区
int maxSize; // 缓冲区的最大容量
std::mutex mtx; // 互斥锁
std::condition_variable bufferFull; // 缓冲区满的条件变量
std::condition_variable bufferEmpty; // 缓冲区空的条件变量
public:
Monitor(int size) : maxSize(size) {}
void produce(int item) {
std::unique_lock<std::mutex> lock(mtx);
while (buffer.size() == maxSize) {
bufferFull.wait(lock); // 缓冲区满,生产者等待
}
buffer.push(item);
std::cout << "Produced item: " << item << std::endl;
bufferEmpty.notify_one(); // 通知一个消费者
}
int consume() {
std::unique_lock<std::mutex> lock(mtx);
while (buffer.empty()) {
bufferEmpty.wait(lock); // 缓冲区空,消费者等待
}
int item = buffer.front();
buffer.pop();
std::cout << "Consumed item: " << item << std::endl;
bufferFull.notify_one(); // 通知一个生产者
return item;
}
};
Monitor monitor(5); // 缓冲区大小为5
void producer() {
for (int i = 1; i <= 10; ++i) {
monitor.produce(i);
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 生产间隔500ms
}
}
void consumer() {
for (int i = 1; i <= 10; ++i) {
int item = monitor.consume();
std::this_thread::sleep_for(std::chrono::milliseconds(800)); // 消费间隔800ms
}
}
int main() {
std::thread producerThread(producer);
std::thread consumerThread(consumer);
producerThread.join();
consumerThread.join();
return 0;
}
这个示例中,我们创建了一个Monitor
类,它包含了一个共享的缓冲区buffer
、缓冲区的最大容量maxSize
、互斥锁mtx
和两个条件变量bufferFull
和bufferEmpty
。
生产者通过调用monitor.produce(item)
来生产一个物品,并将其放入缓冲区中。如果缓冲区已满,则生产者线程会等待,直到有消费者消费一个物品并通知生产者。
消费者通过调用monitor.consume()
来从缓冲区中消费一个物品。如果缓冲区为空,则消费者线程会等待,直到有生产者生产一个物品并通知消费者。
在主函数中,我们创建了一个生产者线程和一个消费者线程,并让它们并发运行。生产者生产10个物品,消费者消费10个物品。每个生产和消费操作之间有一定的时间间隔,以便观察到生产者和消费者的交替执行。
通过使用管程,我们保证了生产者和消费者之间的同步和互斥访问,避免了缓冲区的竞争条件,实现了正确而安全的生产者-消费者问题的解决方案。