相关:互斥锁、条件变量、原子操作、windows临界区、线程同步、解耦、设计类
BlockQueue使用了互斥和同步,来使队列中的元素达到平衡。
producer和consumer如果需要,要设计使用自己的互斥及同步。
//BlockQueue.h
#pragma once//为了防止头文件的重复引用
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#define TASK_NUM 8
using namespace std;
class BlockQueue
{
private:
mutex mt_; //互斥锁
condition_variable cv_con_; //条件变量,在使用处生效,用于线程间的同步和通信
condition_variable cv_prod_;
//在存储方面相当于一个变量,此处只能存储true和false
atomic<bool> stopped_; //模板类,用于实现线程安全的原子操作
queue<int> tasks_; //容器适配器,是一种机制(先进先出)
const int capacity_; //队列容量大小
private:
bool stopped() {
return stopped_.load(); //获取原子变量的当前值,读
}
bool empty() {
return tasks_.size() == 0 ? true : false; //队列中元素个数
}
bool full() {
return tasks_.size() == capacity_ ? true : false;
}
public:
BlockQueue();
~BlockQueue();
void stop() {
stopped_.store(true);//将给定的值存储到原子对象中,写
}
bool available() {
return !stopped() || !empty();
}
void push(const int& data);
void pop(int& data);
};
//BlockQueue.cpp
#include "BlockQueue.h"
BlockQueue::BlockQueue() : capacity_(TASK_NUM), stopped_(false)
{
}
BlockQueue::~BlockQueue()
{
stop();
cv_con_.notify_all(); //唤醒所有等待在条件变量上的线程
cv_prod_.notify_all();
}
void BlockQueue::push(const int& data)
{
//通过析构函数帮助mutex解锁,避免死锁发生https://blog.youkuaiyun.com/weixin_42127358/article/details/123507748
//保护上锁后的代码,防止对资源的竞争。尽量只在公共资源前后
unique_lock<mutex> lck(mt_); //mutex为基本互斥锁类(访问保护),unique_lock提供互斥锁的管理
while (full()) {
cv_con_.notify_one(); //唤醒一个等待在条件变量上的线程
cv_prod_.wait(lck); //使当前线程挂起,直到唤醒
}
tasks_.push(data);
cv_con_.notify_one();
}
void BlockQueue::pop(int& data)
{
unique_lock<mutex> lck(mt_);
while (empty()) {
if (this->stopped())
return;
cv_prod_.notify_one(); //如果正在等待的线程多于一个,则唤醒的线程是不确定的;没有等待,则不执行任何操作
cv_con_.wait(lck, [this]() {
return this->stopped() || !this->empty();
}); //参数为互斥锁和等待条件
}
data = tasks_.front(); //返回首元素或尾元素
tasks_.pop(); //移除首元素
cv_prod_.notify_one();
}
//main.cpp
#include <iostream>
#include <Windows.h>
#include "BlockQueue.h"
using namespace std;
//临界区,为访问临界资源的那段代码称(临界资源是一次仅允许一个进程使用的共享资源)
//相当于一个互斥锁,用于控制对临界区的访问
CRITICAL_SECTION cs;
void consumer(BlockQueue* bq) {
while (bq->available()) {
int data = -1;
bq->pop(data);
EnterCriticalSection(&cs); //标识一个临界区
cout << "<" << this_thread::get_id() << ">:" << data << "comsumed.\n"; //获取线程标识符
LeaveCriticalSection(&cs); //释放一个临界区
}
cout << "[" << this_thread::get_id() << "]:" << "consumer is done.\n";
}
void producer(BlockQueue* bq, int start, int max_num) {
int i = 0;
while (i++ < max_num) {
int data = i + start;
bq->push(data);
EnterCriticalSection(&cs);
cout << "[" << this_thread::get_id() << "]:" << data << "produced.\n"; //获取线程标识符
LeaveCriticalSection(&cs);
}
cout << "[" << this_thread::get_id() << "]:" << "producer is done.\n";
}
int main() {
BlockQueue bqueue;
InitializeCriticalSection(&cs); //初始化CRITICAL_SECTION
vector<thread> th_prods;
const int num_prod = 3;
for (int i = 0; i < num_prod; ++i) {
//emplace_back允许在容器内部构造元素,此处后面三个形参,为第一个形参的参数
//在尾部创建一个元素,使用函数指针作为线程的执行函数
th_prods.emplace_back(producer, &bqueue, i * 100, num_prod * 10);
}
vector<thread> th_cons;
const int num_con = 3;
for (int i = 0; i < num_con; ++i) {
th_cons.emplace_back(consumer, &bqueue);
}
for (auto &t : th_prods) {
t.join(); //等待线程结束
}
for (auto& t : th_cons) {
t.join();
}
DeleteCriticalSection(&cs); //删除临界区
return 0;
}