我正在用c ++实现一个生产者消费者项目,当我运行该程序时,同一个消费者几乎抓住了所有的工作,而不让任何其他消费者线程抓住任何东西。有时,其他线程确实可以获得一些工作,但是其他线程会控制一段时间。例如,TID 10可以抓住几乎所有的工作,但是突然之间TID 12会抓住它,没有其他消费者线程在其间工作。
知道为什么其他线程没有机会抓住工作?
#include <thread>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <deque>
#include <csignal>
#include <unistd.h>
using namespace std;
int max_queue_size = 100;
int num_producers = 5;
int num_consumers = 7;
int num_operations = 40;
int operations_created = 0;
thread_local int operations_created_by_this_thread = 0;
int operations_consumed = 0;
thread_local int operations_consumed_by_this_thread = 0;
struct thread_stuff {
int a;
int b;
int operand_num;
char operand;
};
char operands[] = {'+', '-', '/', '*'};
deque<thread_stuff> q;
bool finished = false;
condition_variable cv;
mutex queue_mutex;
void producer(int n) {
while (operations_created_by_this_thread < num_operations) {
int oper_num = rand() % 4;
thread_stuff equation;
equation.a = rand();
equation.b = rand();
equation.operand_num = oper_num;
equation.operand = operands[oper_num];
while ((operations_created - operations_consumed) >= max_queue_size) {
// don't do anything until it has space available
}
{
lock_guard<mutex> lk(queue_mutex);
q.push_back(equation);
operations_created++;
}
cv.notify_all();
operations_created_by_this_thread++;
this_thread::__sleep_for(chrono::seconds(rand() % 2), chrono::nanoseconds(0));
}
{
lock_guard<mutex> lk(queue_mutex);
if(operations_created == num_operations * num_producers){
finished = true;
}
}
cv.notify_all();
}
void consumer() {
while (true) {
unique_lock<mutex> lk(queue_mutex);
cv.wait(lk, [] { return finished || !q.empty(); });
if(!q.empty()) {
thread_stuff data = q.front();
q.pop_front();
operations_consumed++;
operations_consumed_by_this_thread++;
int ans = 0;
switch (data.operand_num) {
case 0:
ans = data.a + data.b;
break;
case 1:
ans = data.a - data.b;
break;
case 2:
ans = data.a / data.b;
break;
case 3:
ans = data.a * data.b;
break;
}
cout << "Operation " << operations_consumed << " processed by PID " << getpid()
<< " TID " << this_thread::get_id() << ": "
<< data.a << " " << data.operand << " " << data.b << " = " << ans << " queue size: "
<< (operations_created - operations_consumed) << endl;
}
this_thread::yield();
if (finished) break;
}
}
void usr1_handler(int signal) {
cout << "Status: Produced " << operations_created << " operations and "
<< (operations_created - operations_consumed) << " operations are in the queue" << endl;
}
void usr2_handler(int signal) {
cout << "Status: Consumed " << operations_consumed << " operations and "
<< (operations_created - operations_consumed) << " operations are in the queue" << endl;
}
int main(int argc, char *argv[]) {
if (argc < 5) {
cout << "Invalid number of parameters passed in" << endl;
exit(1);
}
max_queue_size = atoi(argv[1]);
num_operations = atoi(argv[2]);
num_producers = atoi(argv[3]);
num_consumers = atoi(argv[4]);
// signal(SIGUSR1, usr1_handler);
// signal(SIGUSR2, usr2_handler);
thread producers[num_producers];
thread consumers[num_consumers];
for (int i = 0; i < num_producers; i++) {
producers[i] = thread(producer, num_operations);
}
for (int i = 0; i < num_consumers; i++) {
consumers[i] = thread(consumer);
}
for (int i = 0; i < num_producers; i++) {
producers[i].join();
}
for (int i = 0; i < num_consumers; i++) {
consumers[i].join();
}
cout << "finished!" << endl;
}
答案 0 :(得分:1)
你一直持有互斥锁 - 包括yield()
- 持有互斥锁。
在您的生产者代码中执行unique_lock的范围,从队列中弹出并以原子方式递增计数器。
我看到你有一个最大队列大小。如果队列已满,那么生产者需要第二个条件才能等待,消费者会在消耗物品时发出这种情况。
答案 1 :(得分:1)
知道为什么其他线程没有机会抓住工作?
这项民意调查令人不安:
while ((operations_created - operations_consumed) >= max_queue_size)
{
// don't do anything until it has space available
}
你可能会在循环中尝试最小的延迟...这是一个“坏邻居”,并且可以“消费”#39;核心。
答案 2 :(得分:0)
您的代码存在一些问题:
以下是一个例子:
int operations_created = 0;
int operations_consumed = 0;
void producer(int n) {
[...]
while ((operations_created - operations_consumed) >= max_queue_size) { }
以后
void consumer() {
[...]
operations_consumed++;
这仅适用于没有优化的x86架构,即-O0
。一旦我们尝试启用优化,编译器就会将while循环优化为:
void producer(int n) {
[...]
if ((operations_created - operations_consumed) >= max_queue_size) {
while (true) { }
}
所以,你的程序只是挂在这里。你可以check this on Compiler Explorer.
mov eax, DWORD PTR operations_created[rip]
sub eax, DWORD PTR operations_consumed[rip]
cmp eax, DWORD PTR max_queue_size[rip]
jl .L19 // here is the if before the loop
.L20:
jmp .L20 // here is the empty loop
.L19:
为什么会这样?从单线程程序的角度来看,如果while (condition) { operators }
不更改if (condition) while (true) { operators }
,则operators
完全等同于condition
。
要解决此问题,我们应使用std::atomic<int>
代替简单int
。这些是为线程间通信而设计的,因此编译器将避免这种优化并生成正确的程序集。
yield()
看一下这个片段:
void consumer() {
while (true) {
unique_lock<mutex> lk(queue_mutex);
[...]
this_thread::yield();
[...]
}
基本上这意味着消费者持有yield()
持有锁。由于只有一个消费者可以一次锁定(互斥体代表互斥),这就解释了为什么其他消费者无法消费这项工作。
要解决此问题,我们应在queue_mutex
之前解锁yield()
,即:
void consumer() {
while (true) {
{
unique_lock<mutex> lk(queue_mutex);
[...]
}
this_thread::yield();
[...]
}
这仍然不能保证只有一个线程可以完成大部分任务。当我们在生产者中执行notify_all()
时,所有线程都会被唤醒,但只有一个会锁定互斥锁。由于我们安排的工作很小,当生产者调用notify_all()
我们的线程将完成工作时,完成yield()
并准备好接下来的工作。
那么为什么这个线程锁定互斥锁,而不是另一个呢?我想这是由于CPU缓存和忙碌等待而发生的。刚完成工作的线程是“热门”,它在CPU缓存中并准备锁定互斥锁。在进入睡眠状态之前,它也可能会尝试忙于等待互斥几个周期,从而增加了获胜的机会。
要解决此问题,我们可以删除生产者中的睡眠(因此它会更频繁地唤醒其他线程,因此其他线程也会“热”),或者在消费者中执行sleep()
yield()
的{(因此这个线程在睡眠期间变得“冷”)。
无论如何,由于互斥锁,没有机会并行完成这项工作,所以同一个线程完成大部分工作的事实是完全自然的IMO。