簡體   English   中英

在 C++ 中關閉同步隊列會阻塞線程

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM