繁体   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