C++全平台消息队列库

消息队列的核心作用其实很简单,一个或多个线程往一个队列后面堆数据,另外的一个线程从队列前面取数据处理,基本操作也只有两个,一个发,一个收,但是传输的数据格式任意不限。一般的消息队列库可能只支持传基本的数据结构,大型的数据只能使用指针,不方便调试,有损代码的易读性:
来源于一个开源项目https://github.com/khuttun/PolyM

非常感谢khuttun的无私贡献

下面是描述:

PolyM

PolyM is a very simple C++ message queue intended for inter-thread communication. There are three major requirement driving the design of PolyM:

  • The design should be simple and lightweight
  • The design should support delivering any kind of data as the message payload
  • There should be no copies made of the message payload data when passing the message through the queue

PolyM (Polymorphic Message Queue) fulfills these requirements by usage of C++ move semantics.

Moving Messages Through the Queue

When a message with payload data (PolyM::DataMsg<PayloadType>) is created, the payload is allocated from the heap. When the message is put to the queue, it is moved there with so called “virtual move constructor”. The virtual move constructor is what enables the polymorphism of the message queue: any message type derived from the base PolyM::Msg type can be moved to the queue. When a message is read from the queue, it is moved out from the queue to be the responsibility of the receiver.

All the message types are movable, but not copyable. Once the message is put to the queue, the sender cannot access the message data anymore. A message always has a single owner, and on send-receive scenario, the ownership is transferred

Sender -> Queue -> Receiver

Messaging Patterns

PolyM supports two kinds of messaging patterns:

  • One way
    • Messages are put to the queue with PolyM::Queue::put(). put() will return immediately.
    • Messages are read from the queue with PolyM::Queue::get(). get() will block until there is at least one message available in the queue (or until timeout occurs, if specified).
  • Request-response
    • A request is made with PolyM::Queue::request(). request() will block until a response is given. The request message can be read from the queue just as any other message with get().
    • A response is given with PolyM::Queue::respondTo(). respondTo() will return immediately.

以下是测试代码:
Tester.hpp

#ifndef TESTER_HPP
#define TESTER_HPP

#include <cstdlib>
#include <iostream>
#include <functional>
#include <utility>Tester.hpp
#include <string>
#include <vector>

// This file contains an implementation for *very* simple testing framework.

// Use these macros in your test functions to test for various things.
// The test program will print an error message and exit if the tests fail.

/** Test that boolean condition a is true */
#define TEST(a) test((a), #a, __FILE__, __LINE__);
/** Test that a == b */
#define TEST_EQUALS(a, b) testEquals((a), (b), __FILE__, __LINE__);
/** Test that a != b */
#define TEST_NOT_EQUALS(a, b) testNotEquals((a), (b), __FILE__, __LINE__);
/** Test that a < b */
#define TEST_LESS_THAN(a, b) testLessThan((a), (b), __FILE__, __LINE__);
/** Fail the test with error message msg */
#define TEST_FAIL(msg) testFail((msg), __FILE__, __LINE__);

void test(bool a, const char* exp, const char* file, int line)
{
    if (!a)
    {
        std::cout << file << ":" << line << ": TEST FAILED: " << exp << std::endl;
        exit(1);
    }
}

template <class C> void testEquals(C a, C b, const char* file, int line)
{
    if (a != b)
    {
        std::cout << file << ":" << line << ": TEST FAILED: " << a << " == " << b << std::endl;
        exit(1);
    }
}

template <class C> void testNotEquals(C a, C b, const char* file, int line)
{
    if (a == b)
    {
        std::cout << file << ":" << line << ": TEST FAILED: " << a << " != " << b << std::endl;
        exit(1);
    }
}

template <class C> void testLessThan(C a, C b, const char* file, int line)
{
    if (a >= b)
    {
        std::cout << file << ":" << line << ": TEST FAILED: " << a << " < " << b << std::endl;
        exit(1);
    }
}

void testFail(const char* msg, const char* file, int line)
{
    std::cout << file << ":" << line << ": TEST FAILED: " << msg << std::endl;
    exit(1);
}

/**
 * Tester is a container for tests.
 * Use addTest() to add new test.
 * Use runTests() to run all added tests.
 */
class Tester
{
public:
    /**
     * Construct a Tester.
     *
     * @param name Name for the Tester instance. Will be printed when running tests.
     */
    Tester(const std::string& name)
      : name_(name), tests_()
    {
    }

    /**
     * Add test.
     *
     * @param testFunc Function containing the test.
     * @param testDesc Description for the test. Will be printed when running the test.
     */
    void addTest(const std::function<void()>& testFunc, const std::string& testDesc)
    {
        tests_.push_back(std::make_pair(testFunc, testDesc));
    }

    /**
     * Run all added tests.
     */
    void runTests() const
    {
        std::cout << name_ << ": Running " << tests_.size() << " tests" << std::endl;
        for (auto& test : tests_)
        {
            std::cout << test.second << "... ";
            test.first();
            std::cout << "OK!" << std::endl;
        }
    }

private:
    std::string name_;
    std::vector<std::pair<std::function<void()>, std::string>> tests_;
};

#endif

test.cpp

#include "Queue.hpp"
#include "Tester.hpp"
#include <functional>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>

// Test that MsgUIDs generated in two different threads simultaneously are unique
void testMsgUID()
{
    const int N = 1000;
    std::vector<PolyM::MsgUID> uids1, uids2;

    auto createMsgs = [](int count, std::vector<PolyM::MsgUID>& uids)
    {
        for (int i = 0; i < count; ++i)
            uids.push_back(PolyM::Msg(1).getUniqueId());
    };

    std::thread t1(createMsgs, N, std::ref(uids1));
    std::thread t2(createMsgs, N, std::ref(uids2));
    t1.join();
    t2.join();

    for (auto uid1 : uids1)
    {
        for (auto uid2 : uids2)
        {
            TEST_NOT_EQUALS(uid1, uid2);
        }
    }
}

// Test that messages are received in order in 1-to-1 one-way messaging scenario
void testMsgOrder()
{
    const int N = 1000;
    PolyM::Queue queue;

    auto sender = [](int count, PolyM::Queue& q)
    {
        for (int i = 0; i < count; ++i)
            q.put(PolyM::Msg(i));
    };

    auto receiver = [](int count, PolyM::Queue& q)
    {
        for (int i = 0; i < count; ++i)
        {
            auto m = q.get();
            TEST_EQUALS(m->getMsgId(), i);
        }
    };

    std::thread t1(sender, N, std::ref(queue));
    std::thread t2(receiver, N, std::ref(queue));
    t1.join();
    t2.join();
}

// Test that messages are received in order in 2-to-1 one-way messaging scenario
void test2To1MsgOrder()
{
    const int N = 1000;
    PolyM::Queue queue;

    auto sender = [](int msgId, int count, PolyM::Queue& q)
    {
        for (int i = 0; i < count; ++i)
            q.put(PolyM::DataMsg<int>(msgId, i));
    };

    auto receiver = [](int count, PolyM::Queue& q)
    {
        int expectedData1 = 0;
        int expectedData2 = 0;

        for (int i = 0; i < count; ++i)
        {
            auto m = q.get();
            auto& dm = dynamic_cast<PolyM::DataMsg<int>&>(*m);

            if (dm.getMsgId() == 1)
            {
                TEST_EQUALS(dm.getPayload(), expectedData1);
                ++expectedData1;
            }
            else if (dm.getMsgId() == 2)
            {
                TEST_EQUALS(dm.getPayload(), expectedData2);
                ++expectedData2;
            }
            else
            {
                TEST_FAIL("Unexpected message id");
            }
        }
    };

    std::thread t1(sender, 1, N, std::ref(queue));
    std::thread t2(sender, 2, N, std::ref(queue));
    std::thread t3(receiver, 2 * N, std::ref(queue));
    t1.join();
    t2.join();
    t3.join();
}

// Test putting DataMsg through the queue
void testDataMsg()
{
    PolyM::Queue q;
    q.put(PolyM::DataMsg<std::string>(42, "foo"));
    auto m = q.get();
    auto& dm = dynamic_cast<PolyM::DataMsg<std::string>&>(*m);
    TEST_EQUALS(dm.getMsgId(), 42);
    TEST_EQUALS(dm.getPayload(), std::string("foo"));
    // Test modifying the payload data
    dm.getPayload() += "bar";
    TEST_EQUALS(dm.getPayload(), std::string("foobar"));
}

// Test putting DataMsg through the queue
void testMatMsg()
{
    PolyM::Queue q;
    cv::Mat ones =cv::imread("/home/shining/Pictures/617286e5cc296b0bb03d1cbd0d318f9c.jpg");
    q.put(PolyM::DataMsg<cv::Mat>(42, ones));
    auto m = q.get();
    auto& dm = dynamic_cast<PolyM::DataMsg<cv::Mat>&>(*m);
    TEST_EQUALS(dm.getMsgId(), 42);
    TEST_EQUALS(dm.getPayload().cols, ones.cols);
    TEST_EQUALS(dm.getPayload().rows, ones.rows);
    // Test modifying the payload data
    auto am = dm.getPayload();
    cv::imwrite("save.jpg",am);
}


// Test timeout when getting message from the queue
void testReceiveTimeout()
{
    PolyM::Queue q;

    // Test first with a Msg in the queue that specifying timeout for get() doesn't have an effect
    q.put(PolyM::Msg(1));

    auto start = std::chrono::steady_clock::now();
    auto m = q.get(10);
    auto end = std::chrono::steady_clock::now();
    auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();

    TEST_EQUALS(m->getMsgId(), 1);
    //TEST_LESS_THAN(dur, 5LL);

    // Then test with empty queue
    start = std::chrono::steady_clock::now();
    auto m2 = q.get(10);
    end = std::chrono::steady_clock::now();
    dur = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    printf("\n testReceiveTimeout:%5d \n",dur);
    TEST_EQUALS(m2->getMsgId(), PolyM::MSG_TIMEOUT);
    //TEST_LESS_THAN(5LL, dur);
    //TEST_LESS_THAN(dur, 15LL);
}

// Test 2-to-1 request-response scenario
void testRequestResponse()
{
    const int N = 1000;

    PolyM::Queue queue;

    auto requester1 = [](int count, PolyM::Queue& q)
    {
        for (int i = 0; i < count; ++i)
        {
            TEST_EQUALS(q.request(PolyM::Msg(i))->getMsgId(), i + count);
        }
    };

    auto requester2 = [](int count, PolyM::Queue& q)
    {
        for (int i = 0; i < count; ++i)
        {
            TEST_EQUALS(q.request(PolyM::Msg(i + 2 * count))->getMsgId(), i + 3 * count);
        }
    };

    auto responder = [](int count, PolyM::Queue& q)
    {
        for (int i = 0; i < 2 * count; ++i)
        {
            auto m = q.get();
            q.respondTo(m->getUniqueId(), PolyM::Msg(m->getMsgId() + count));
        }
    };

    std::thread t1(requester1, N, std::ref(queue));
    std::thread t2(requester2, N, std::ref(queue));
    std::thread t3(responder, N, std::ref(queue));
    t1.join();
    t2.join();
    t3.join();
}

int main()
{
    // Statically assert that messages can't be copied or moved
    static_assert(!std::is_move_constructible<PolyM::Msg>::value, "Msg can't be copyable");
    static_assert(!std::is_move_assignable<PolyM::Msg>::value, "Msg can't be copyable");
    static_assert(!std::is_move_constructible<PolyM::DataMsg<int>>::value, "DataMsg can't be copyable");
    static_assert(!std::is_move_assignable<PolyM::DataMsg<int>>::value, "DataMsg can't be copyable");

    Tester tester("Test PolyM");
    tester.addTest(testMsgUID, "Test MsgUID generation");
    tester.addTest(testMsgOrder, "Test 1-to-1 message order");
    tester.addTest(test2To1MsgOrder, "Test 2-to-1 message order");
    tester.addTest(testDataMsg, "Test DataMsg");
    tester.addTest(testMatMsg, "Test MatMsg");
    tester.addTest(testReceiveTimeout, "Test receive timeout");
    tester.addTest(testRequestResponse, "Test 2-to-1 request-response");
    tester.runTests();
}

CMakelist.txt

cmake_minimum_required(VERSION 2.8)
project(PolyM)

set(OpenCV_DIR /home/shining/ProgramFiles/miniconda/envs/pytorch/share/OpenCV)
find_package (OpenCV REQUIRED NO_CMAKE_FIND_ROOT_PATH)

# If the package has been found, several variables will
# be set, you can find the full list with descriptions
# in the OpenCVConfig.cmake file.
# Print some message showing some of them
message(STATUS "OpenCV library status:")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")
set(CMAKE_CXX_FLAGS "-std=c++11 -pedantic -Wall -Wextra -Weffc++ -O3")
add_library(polym STATIC Msg.cpp Queue.cpp)
add_executable(testpolym Test.cpp )
target_link_libraries(testpolym  polym )
target_link_libraries(testpolym  "pthread"  "opencv_core" "opencv_highgui" "opencv_imgcodecs" "opencv_imgproc")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值