📜  生产者消费者问题及其C++实现(1)

📅  最后修改于: 2023-12-03 14:56:15.816000             🧑  作者: Mango

生产者消费者问题及其C++实现

生产者消费者问题是一个多线程同步的经典问题,它包含两类线程:生产者和消费者。生产者生成数据并放入缓冲区,而消费者从缓冲区取出数据并消费它们。缓冲区的大小是有限的,因此生产者必须等待消费者消费数据以便继续生成数据。同样地,消费者必须等待生产者生成数据以便消费数据。这就是一个典型的同步问题。

C++实现
使用互斥锁和条件变量

在C++中,我们通常使用互斥锁和条件变量来实现生产者消费者问题。互斥锁用于同步访问共享资源,条件变量用于线程之间的通信。下面是一个使用互斥锁和条件变量实现的生产者消费者问题的示例:

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

std::queue<int> data;  // 缓冲区
std::mutex mutex;  // 互斥锁
std::condition_variable cond;  // 条件变量

void producer() {
  int i = 1;
  while (i <= 10) {
    std::unique_lock<std::mutex> lock(mutex);
    data.push(i);  // 生产数据
    std::cout << "Producer: produced " << i << std::endl;
    i++;
    lock.unlock();  // 解锁
    cond.notify_one();  // 唤醒一个消费者
    std::this_thread::sleep_for(std::chrono::milliseconds(500));  // 等待一段时间
  }
}

void consumer() {
  int data_value;
  while (true) {
    std::unique_lock<std::mutex> lock(mutex);
    while (data.empty()) {  // 等待直到有数据
      cond.wait(lock);
    }
    data_value = data.front();  // 取出数据
    data.pop();
    std::cout << "Consumer: consumed " << data_value << std::endl;
    lock.unlock();  // 解锁
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));  // 等待一段时间
  }
}

int main() {
  std::thread t1(producer);
  std::thread t2(consumer);
  t1.join();
  t2.join();
  return 0;
}

代码说明:

  • 生产者和消费者分别运行在不同的线程中,它们共享一个缓冲区。
  • 当生产者生产数据并放入缓冲区后,它会通知消费者。如果缓冲区已满,生产者会等待消费者消费数据。
  • 当消费者从缓冲区取出数据并消费完后,它会通知生产者。如果缓冲区为空,消费者会等待生产者生成数据。
  • 使用互斥锁保证了对缓冲区的访问是互斥的,避免了竞争条件出现。
  • 使用条件变量使得生产者和消费者能够等待对方的通知,避免了忙等待的情况。
使用信号量

除了使用互斥锁和条件变量外,我们还可以使用信号量来实现生产者消费者问题。信号量是一种同步原语,它可以用来协调多个线程的活动。下面是一个使用信号量实现的生产者消费者问题的示例:

#include <iostream>
#include <queue>
#include <thread>
#include <semaphore.h>

std::queue<int> data;  // 缓冲区
sem_t sem_empty;  // 空信号量
sem_t sem_full;  // 满信号量
std::mutex mutex;  // 互斥锁

void producer() {
  int i = 1;
  while (i <= 10) {
    sem_wait(&sem_empty);  // 等待空信号量
    std::unique_lock<std::mutex> lock(mutex);
    data.push(i);  // 生产数据
    std::cout << "Producer: produced " << i << std::endl;
    i++;
    lock.unlock();  // 解锁
    sem_post(&sem_full);  // 发送满信号量
    std::this_thread::sleep_for(std::chrono::milliseconds(500));  // 等待一段时间
  }
}

void consumer() {
  int data_value;
  while (true) {
    sem_wait(&sem_full);  // 等待满信号量
    std::unique_lock<std::mutex> lock(mutex);
    data_value = data.front();  // 取出数据
    data.pop();
    std::cout << "Consumer: consumed " << data_value << std::endl;
    lock.unlock();  // 解锁
    sem_post(&sem_empty);  // 发送空信号量
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));  // 等待一段时间
  }
}

int main() {
  sem_init(&sem_empty, 0, 10);  // 初始化空信号量
  sem_init(&sem_full, 0, 0);  // 初始化满信号量
  std::thread t1(producer);
  std::thread t2(consumer);
  t1.join();
  t2.join();
  sem_destroy(&sem_empty);  // 销毁空信号量
  sem_destroy(&sem_full);  // 销毁满信号量
  return 0;
}

代码说明:

  • 生产者和消费者分别运行在不同的线程中,它们共享一个缓冲区。
  • 使用两个信号量sem_empty和sem_full来协调生产者和消费者的活动。
  • 对于每个缓冲区中的数据,信号量sem_full被递增,而对于每个空的缓冲区,信号量sem_empty被递增。
  • 在每个线程中,当一个信号量的值为0时,调用sem_wait函数会阻塞该线程,直到该信号量的值不为0。当收到信号时,线程会被唤醒并继续运行。
  • 使用互斥锁保证了对缓冲区的访问是互斥的,避免了竞争条件出现。
总结

生产者消费者问题是一个多线程同步的经典问题,需要借助互斥锁、条件变量或信号量等同步原语来实现。在实现过程中,需要考虑到缓冲区的大小、生产者和消费者之间的同步和互斥等问题。在C++中,使用std::mutex、std::condition_variable和sem_t等函数来实现同步和互斥操作。