简体   繁体   English

CPP:这是关于线程安全 FIFIO 消息队列的 go 的合理方法吗

[英]CPP: Is this a reasonable way to go about thread safe FIFIO message queues

Please be gentle - first time I've posted here.请保持温和 - 我第一次在这里发帖。 Currently teaching myself C++.目前正在自学C++。 Wanted to play with threads and passing messages of any type between them.想玩线程并在它们之间传递任何类型的消息。 Not got to the thread part yet - just working on the messaging queue element.还没有进入线程部分 - 只是处理消息队列元素。 Below is the code I've put together that works - at least in a single main() thread.下面是我放在一起的代码 - 至少在一个 main() 线程中。 Before I start creating custom data objects and thread functions - I wanted to get a consensus if what I have done so far is viable (if not not fully aligned with convention/standards or wholly efficient) - so really just looking for direction if this is going to create unnecessary complexities later down the line with threads.在我开始创建自定义数据对象和线程函数之前——我想就我到目前为止所做的事情是否可行(如果不完全符合约定/标准或完全有效)达成共识——所以如果这是真的只是在寻找方向稍后将使用线程创建不必要的复杂性。

So two code files:所以两个代码文件:

messageQueue.h which includes the implementation as well as header as couldn't get the linker to work with the implementation being in a separate cpp file. messageQueue.h包括实现以及 header 无法让 linker 与位于单独 cpp 文件中的实现一起工作。

#pragma once
#include<queue>         // for queue
#include<thread>        // for smart pointers
#include<mutex>         // for locking of the queue when doing push(), front() and pop()



/*
Purpose:
To provide a thread safe method of of passing messages between threads in a FIFO queue manner.
Currently this uses unique_ptr which enables a one to one publisher/consumer application, if
we need one to many publisher/consumer model then will need to investigate maintaining a collection 
of subscribers (consumers) and use shared_ptr.
Usage notes:
Message objects need to be created using make_unique not using new - e.g.
    auto messagePtr = std::make_unique<message_t<std::string>>("this is a message");
Call this way: 
    messageQueue<objectType> messageQueueName;
    messageQueueName.publish(std::move(messagePtr));
    messagePtr = messageQueueName.consume();
*/

template<class T>
class message_t
{
public:
    message_t(T message) : m_message{ message } {}

private:
    T m_message;
};

template<class T>
class messageQueue
{
public:
    messageQueue() {};
    void publish(std::unique_ptr<message_t<T>> messagePtr);
    std::unique_ptr<message_t<T>> consume();
    bool hasData();
private:
    std::queue < std::unique_ptr<message_t<T>>> m_queue;
    std::mutex m_mutex;

};

// Had to add the implementation into the header file as templated classes have link issues when in separate cpp files :-(

template<class T>
void messageQueue<T>::publish(std::unique_ptr<message_t<T>> messagePtr) {
    std::lock_guard<std::mutex> lock(m_mutex); 
    m_queue.push(std::move(messagePtr));
};

template<class T>
std::unique_ptr<message_t<T>> messageQueue<T>::consume() {
    std::unique_ptr<message_t<T>> retVal;
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!m_queue.empty()) { retVal = std::move(m_queue.front()); }
        m_queue.pop();
    }

    return retVal;
};

template<class T>
bool messageQueue<T>::hasData() {
    std::lock_guard<std::mutex> lock(m_mutex);
    return(!m_queue.empty());
};

main.cpp - just has a very simple test case in it. main.cpp - 里面只有一个非常简单的测试用例。

#include "messageQueue.h"
#include<string>
#include<iostream>


int main(void) {
    messageQueue<std::string> textQueue;                        // Create the message queue 
    auto messagePtr = std::make_unique<message_t<std::string>>("this is a message");    // Create a message and get its unique pointer
    std::cout << "messagePtr= " << messagePtr << std::endl;     // output the pointer value
    textQueue.publish(std::move(messagePtr));                   // Push the message pointer onto the queue : messagePtr is now 0
    auto newMessagePtr = std::make_unique<message_t<std::string>>("this is another message");   // Create a second message and get its unique pointer
    std::cout << "newMessagePtr= " << newMessagePtr << std::endl;   // output the new message pointer value
    std::cout << "Consuming messagePtr into newMessagePtr" << std::endl;    //comment
    if (textQueue.hasData()) { newMessagePtr = textQueue.consume(); }   // Pull the original pointer off the queue and assign to new message pointer
    std::cout << "newMessagePtr= " << newMessagePtr << std::endl;   // output the pointer in the new message pointer - should be first pointer value


    return(0);
}

One issue I see is that if message queue is empty, you still pop from it.我看到的一个问题是,如果消息队列为空,您仍然会从中弹出。

And one more conceptual issue: in a multithreaded environemnt hasData() is never meaningful.还有一个概念性问题:在多线程环境中, hasData()永远没有意义。 Whatever it returns it may immediately be invalidated by another thread.无论它返回什么,它都可能立即被另一个线程无效。 So no code can really depend on the return value, unless you accept the uncertainty of its correctness.因此,除非您接受其正确性的不确定性,否则任何代码都不能真正依赖于返回值。

More common approach is to simply try_consume the value unconditionally (and return nullptr if there is no message) - which you basically already implemented as consume .更常见的方法是简单地try_consume无条件地使用该值(如果没有消息则返回 nullptr )-您基本上已经将其实现为consume

And another approach is to have a blocking consume , which halts current thread until some other thread writes into the queue.另一种方法是使用阻塞consume ,它会暂停当前线程,直到其他线程写入队列。 This however requires using signaling, eg through std::condition_variable or atomics.然而,这需要使用信号,例如通过std::condition_variable或 atomics。 This has its pitfalls, but if you are learning it is worth spending the time to understand how it works.这有其陷阱,但如果您正在学习,值得花时间了解它的工作原理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM