[英]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.