[英]Shutdown a synchronized queue in C++ blocks the thread
一个线程应该在无限循环中调用 msg_queue.push_message() ,而另一个线程应该调用msg_queue.pop_message()
。 调用msg_queue.push_message()
那个最终会处理一个信号并调用msg_queue.shutdown()
但是它不会关闭。 从打印语句中,我猜问题在于msg_queue.shutdown()
。
这是我的MessageQueue.hpp
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#include <atomic>
#include "Message.hpp"
#define MAXQUEUE_MSG 10
class MessageQueue{
public :
MessageQueue(){
stop.store(false);
}
void push_message(Message msg){
std::cout << "Start push message\n";
if (!stop.load()){
std::cout << "section push \n";
std::unique_lock<std::mutex> lock(message_queue_mutex);
cv_message_queue.wait(lock, [this]{return pending_message_queue.size() < MAXQUEUE_MSG || stop.load();});
std::cout << "End wait push\n";
if (!stop.load()){
pending_message_queue.push(msg);
}
cv_message_queue.notify_all();
}
std::cout << "End push message\n";
}
Message pop_message(){
std::cout << "Start pop message\n";
Message msg;
if (!stop.load()){
std::cout << "section pop \n";
std::unique_lock<std::mutex> lock(message_queue_mutex);
cv_message_queue.wait(lock, [this]{return !pending_message_queue.empty() || stop.load();});
std::cout << "End wait pop\n";
if (stop.load()){
cv_message_queue.notify_all();
std::cout << "End pop message\n";
return Message(false, -1, "", 0, 0);
}
msg = pending_message_queue.front();
pending_message_queue.pop();
cv_message_queue.notify_all();
}
std::cout << "End pop message\n";
return msg;
}
void shutdown(){
stop.store(true);
cv_message_queue.notify_all();
cv_message_queue.notify_all();
}
private :
std::atomic<bool> stop;
std::queue<Message> pending_message_queue = {};
std::mutex message_queue_mutex;
std::condition_variable cv_message_queue;
};
这是一个输出
Start push message
section push
End pop message
Start Array push
End Array push
Start^CStart push message
Immediately stopping network packet processing.
Writing output
Outputfile written
Link shutdown
Stop : 1
msg_queue shutdown successful
ack_array shutdown successful
udplink succesfully stopped
Stopping thread
编辑:我们应该有 3 个Stopping thread
这是A.hpp
的信号处理程序
void shutdown(int signum){
stop.store(true);
std::cout << "Writing output\n";
output.write_output();
std::cout << "Outputfile written and closed\n";
std::cout << "Link shutdown\n";
std::cout << "Stop : " << stop.load() << "\n";
msg_queue.shutdown();
std::cout << "msg_queue shutdown successful\n";
ack_array.shutdown();
std::cout << "ack_array shutdown successful\n";
udplink.shutdown();
std::cout << "udplink succesfully stopped\n";
cv_ack.notify_all();
for (auto &th : threads){
std::cout << "Stopping thread\n";
msg_queue.shutdown();
th.join();
}
std::cout << "Threads stopped\n";
}
它不会停止运行此函数的线程 2(仍在 A.hpp 中)。
void send_loop_thread(){
Message msg;
while(!stop.load()){
msg = msg_queue.pop_message();
if (stop.load()) break;
ack_array.push_message(msg);
if (stop.load()) break;
udplink.sendMessage(msg);
output.outputSend(msg.get_msg());
}
std::cout << "Send loop successfully stopped\n";
}
在我调用的函数中, ack_array.push_message(msg) 是唯一一个在Ack.hpp
带有条件变量的Ack.hpp
。
这是函数的代码,但我认为它不会导致任何问题。
void push_message(Message msg){
std::cout << "Start Array push\n";
nb_of_msg++;
int res = 0;
if (!stop.load())
{
std::unique_lock<std::mutex> lock(queue_mutex);
cv_queue.wait(lock, [this]{return !queue_of_indexes.empty() || stop.load();});
if (!stop.load()){
res = queue_of_indexes.front();
queue_of_indexes.pop();
}
}
if (!stop.load()){
std::unique_lock<std::mutex> lock2(ack_array_mutex);
ack_array[res] = Ack(msg);
}
std::cout << "End Array push\n";
}
编辑:每个都有自己的停止变量,即 std::atomic。
编辑 2:这是我的新班级。 我已经在互斥锁内停止了。 它仍然阻止一切。
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#include <atomic>
#include <arpa/inet.h>
#include "Message.hpp"
#define MAXQUEUE_MSG 10
class MessageQueue{
public :
MessageQueue(){
stop = false;
}
void push_message(Message msg){
std::cout << "Start push message\n";
{
std::unique_lock<std::mutex> lock(message_queue_mutex);
if (!stop){
std::cout << "section push \n";
cv_message_queue.wait(lock, [this]{return pending_message_queue.size() < MAXQUEUE_MSG || stop;});
std::cout << "End wait push\n";
if (!stop){
pending_message_queue.push(msg);
}
std::cout << "End push message\n";
}
}
cv_message_queue.notify_all();
}
Message pop_message(){
std::cout << "Start pop message\n";
Message msg;
{
std::unique_lock<std::mutex> lock(message_queue_mutex);
if (!stop){
std::cout << "section pop \n";
cv_message_queue.wait(lock, [this]{return !pending_message_queue.empty() || stop;});
std::cout << "End wait pop\n";
if (stop){
cv_message_queue.notify_all();
std::cout << "End pop message\n";
return Message(false, -1, "", 0, 0);
}
msg = pending_message_queue.front();
pending_message_queue.pop();
}
}
cv_message_queue.notify_all();
std::cout << "End pop message\n";
return msg;
}
void shutdown(){
{
std::unique_lock<std::mutex> lock(message_queue_mutex);
std::cout << "Got lock\n";
stop = true;
}
cv_message_queue.notify_all();
cv_message_queue.notify_all();
//cv_size.notify_all();
}
private :
bool stop = false;
std::queue<Message> pending_message_queue = {};
std::mutex message_queue_mutex;
std::condition_variable cv_message_queue;
};
这是一个输出:
Start pop message
section pop
(ctrl+C here)
Immediately stopping network packet processing.
Writing output
Outputfile written and closed
Link shutdown
Stop : 1
udplink succesfully stopped
Got lock
Receive loop successfully stopped
Ack loop successfully stopped
编辑 3:我想我知道出了什么问题。 当 lambda 函数正在执行时,我调用了我的处理程序,但是如果我在 lambda 中放置一个锁,则不再运行任何东西并且仍然没有解决方案。
if (!stop.load()){
所以到目前为止,这个故事是这个执行线程检查这个原子,发现它没有设置,然后愉快地进入if
语句。 此时发生上下文切换,另一个执行线程继续执行以下操作:
stop.store(true);
cv_message_queue.notify_all();
cv_message_queue.notify_all();
所以,这个执行线程现在将stop
设置为true
,并调用notify_all()
两次。 但是,如果您一直非常非常仔细地记录正在发生的事情,您就不需要我告诉您此时绝对没有任何条件在等待任何条件变量。 这些通知消失在一个巨大的空洞中,再也看不到了。 这个执行线程现在可以notify_all()
调用notify_all()
一百次,它们都会消失。 仅当有人主动等待条件变量时才会发生某些事情,无论如何,您无法保证如果某个执行线程等待先前发出信号的条件变量,而没有其他人接听,那么它就会接听信号。 给条件变量发信号保证只唤醒已经在等待条件变量的执行线程。 总结如下:
解除当前等待 *this 的所有线程的阻塞。
重点目前等待。 句号。 没有例外。 如果没有任何东西在等待,则没有任何东西可以畅通无阻。 结束。
此时另一个上下文切换发生,原执行线程被唤醒,并继续等待其消息队列和条件变量。 一个永远保持为空的消息队列,以及一个永远不会再次发出信号的条件变量。
原子语义很难正确。 除非您确切地、准确地知道最后一条指令、每个执行线程在做什么,以及 C++ 线程同步的精确语义,否则某些事情总是会偏离方向,一切都会崩溃。 在涉及中等复杂性的情况下,有时最简单的做法是:1) 坚持使用简单的非原子变量,以及 2) 对变量进行所有更改并至少在所有条件变量都已修改后发出信号,同时保持互斥锁。 cppreference.com说得很直白:
打算修改共享变量的线程必须
获取 std::mutex(通常通过 std::lock_guard)
在持有锁时执行修改
在 std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知持有锁)
“获取std::mutex
”部分似乎是这里的要求。 确实可以正确通知条件变量而无需涉及适当的互斥锁,但必须在保持任何共享状态修改时保持它,并且正确地执行它非常困难,因此大多数参考资料都没有甚至费心去研究它。
以上很可能是显示代码挂起的原因。 您需要重新设计线程同步,使其严格遵守上述规则(以及我提供的链接中描述的其他一些规则)。
TLDR:所有对stop
访问都必须在持有互斥锁时完成,在这一点上,它作为一个原子没有多大用处,它可以是一个普通的bool
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.