每个线程或每个呼叫一个ZeroMQ套接字?

时间:2017-01-30 17:16:44

标签: c++ multithreading zeromq

众所周知,ZeroMQ套接字shall not be shared among application threads
context_t实例可以。

我有一个多线程应用程序,我想让每个线程不时与 REQ/REP -socket交易对手(事件,异常等)交换消息),取决于他们正在做什么(他们正在做非ZeroMQ的东西)。

要向 REQ/REP -socket发送消息,我使用以下函数
(半个C ++半伪代码):

sendMessage:

bool sendMessage(std::string s)
{
    zmq::socket_t socket(globalContext(), ZMQ_REQ);
    socket.connect("ipc://http-concentrator");

    zmq::message_t message(s.size());
    memcpy(message.data(), s.data(), s.size());
    if (!socket.send(message))
        return false;

    // poll on socket for POLLIN with timeout

    socket.recv(&message);
    // do something with message

    return true;
}

需要时从每个线程调用此函数。它创建一个本地套接字,连接,发送消息,并接收响应。在退出时,套接字被断开并被移除(至少我假设它已关闭)。

这样,我不需要费心去维护每个线程中的套接字。这是以每次调用此函数时创建和连接为代价的。

我强调了这段代码,并没有看到重用一个套接字和重新连接实现之间有太大区别。 (我在用例的两侧每秒有20k REP/REQ个事务,包括JSON解码/编码)

问:有更正确的ZeroMQ方法吗?

4 个答案:

答案 0 :(得分:5)

  

Nota Bene:此答案是在O / P从ipk:// transport-class

上的20k TPS更改为140k TPS之前发布的      

:是否有更多正确的ZeroMQ方式这个

     

答:不容易说出什么是"这个" "正确性" -metric

的参数是什么?

鉴于此,以下几点将更为笼统,适用于系统设计阶段推理:

资源利用率开销避免

这一点是双刃剑。一些开销总是与基础设施元素设置和处理(是的,甚至关闭和拆除) REQ - 访问 REQ/REP -pattern和相关的基于套接字的传输类在REQ侧主机和REP侧都会产生一些显着的开销。

你记得很公平,你在一个大约20k TPS的水平上对这个进行了定量测试,并没有观察到这种方法的任何不利影响。还不清楚的是,是否还在同一SUT(被测系统)上进行了体内测试,以便为每个相应设计的比较提供一些基线(并允许确定开销的差异)本身)。

虽然设计良好的框架会隐藏系统内部行为的这一部分来自用户维护的代码,但这并不意味着,它是一个便宜的,而不是免费的处理。

很明显,在 Context() -instance线程中已经完成了工作(...是的,复数在这里是正确的,因为一些高性能代码通过在模式套接字及其相应的I / O线程处理程序之间明确定义的亲和映射,可以从每个Context()实例使用多个I / O线程中受益并对工作负载分布产生积极影响(以便以某种方式平衡,如果不能确定性地调整预期的I / O吞吐量,包括所有相关的开销)。

如果仍然怀疑,人们应该永远记住,命令式编程风格功能或面向对象的方法主要是外部呼叫者的受害者,外部呼叫者决定在哪个时刻和这种" en-slaved "代码执行单元在执行时被调用并被执行。函数/方法没有任何自然的反向回调(抑制)它自己从外部调用者调用的频率,而健壮的设计根本不能仅仅依赖于这种调用的乐观假设不要经常使用XYZ-k TPS(上面引用的20k可能适用于体外测试,但真正的部署可能会改变几个人的命令(无论是人工测试 - 在测试期间,还是没有 - 在某些高峰时段)或用户(系统) - 西班牙语或由于某些技术错误或硬件故障(我们都听过很多次关于NIC卡充斥L1 / L2流量超出所有可以想象的限制等等 - 我们只是不能也不能知道,下次再次发生的时间/地点。)

避免阻塞风险

上述 REQ/REP 可扩展正式通信模式因其陷入外部无法解析的分布式内部死锁的风险而闻名。这总是存在避免的风险。缓解策略可能取决于实际用例的风险价值(需要认证医疗器械,fintech用例,控制循环用例,学术研究论文代码或私人爱好玩具)。 / p>

  

参考: REQ/REP 死锁>>> https://stackoverflow.com/a/38163015/3666197

Fig.1: 为什么在REQ/REP 时使用天真的 [App1] 所有情况都是错误的in_WaitToRecvSTATE_W2R + [App2] in_WaitToRecvSTATE_W2R
主要是REQ-FSA/REP-FSA的无法解决的分布式相互僵局(每个都是有限的-State-Automata等待"其他"移动)并且永远不会到达" next" in_WaitToSendSTATE_W2S 内部状态。

               XTRN_RISK_OF_FSA_DEADLOCKED ~ {  NETWORK_LoS
                                         :   || NETWORK_LoM
                                         :   || SIG_KILL( App2 )
                                         :   || ...
                                         :      }
                                         :
[App1]      ![ZeroMQ]                    :    [ZeroMQ]              ![App2] 
code-control! code-control               :    [code-control         ! code-control
+===========!=======================+    :    +=====================!===========+
|           ! ZMQ                   |    :    |              ZMQ    !           |
|           ! REQ-FSA               |    :    |              REP-FSA!           |
|           !+------+BUF> .connect()|    v    |.bind()  +BUF>------+!           |
|           !|W2S   |___|>tcp:>---------[*]-----(tcp:)--|___|W2R   |!           |
|     .send()>-o--->|___|           |         |         |___|-o---->.recv()     |
| ___/      !| ^  | |___|           |         |         |___| ^  | |!      \___ |
| REQ       !| |  v |___|           |         |         |___| |  v |!       REP |
| \___.recv()<----o-|___|           |         |         |___|<---o-<.send()___/ |
|           !|   W2R|___|           |         |         |___|   W2S|!           |
|           !+------<BUF+           |         |         <BUF+------+!           |
|           !                       |         |                     !           |
|           ! ZMQ                   |         |   ZMQ               !           |
|           ! REQ-FSA               |         |   REP-FSA           !           |
~~~~~~~~~~~~~ DEADLOCKED in W2R ~~~~~~~~ * ~~~~~~ DEADLOCKED in W2R ~~~~~~~~~~~~~
|           ! /\/\/\/\/\/\/\/\/\/\/\|         |/\/\/\/\/\/\/\/\/\/\/!           |
|           ! \/\/\/\/\/\/\/\/\/\/\/|         |\/\/\/\/\/\/\/\/\/\/\!           |
+===========!=======================+         +=====================!===========+

答案 1 :(得分:2)

这是我的(当前)解决方案,在C ++ 11中,您可以将对象分配给thread_local - 存储。将socket_t - 实例staticthread_local存储在函数中会为我提供我正在寻找的功能:

class socketPool
{
    std::string endpoint_;

public:
    socketPool(const std::string &ep) : endpoint_(ep) {}

    zmq::socket_t & operator()()
    {
        thread_local static zmq::socket_t socket(
                globalContext(), 
                ZMQ_REQ);
        thread_local static bool connected;

        if (!connected) {
            connected = true;
            socket.connect(endpoint_);
        }

        return socket;
    }
};

// creating a pool for each endpoint
socketPool httpReqPool("ipc://http-concentrator");

在我的sendMessage() - 函数中,而不是创建和连接我只需执行

bool sendMessage(std::string s)
{
    zmq::socket_t &socket = httpReqPool();

    // the rest as above
}

关于性能,它在我的机器上快了7倍。 (每秒140k REQ/REP)。

答案 2 :(得分:1)

我认为一个不同的是表现。

使用上面的代码,这意味着你需要做20k次的创建套接字,建立连接,发送消息和关闭套接字,这从我的角度来看是耗时的,你可以运行一些性能工具分析来检查使用了多少时间函数sendMessage()

另一种方法可能为每个线程创建一个请求套接字,并使用它所属的那个线程的套接字发送数据。 ZeroMQ不支持多线程,否则会导致错误,例如断言错误(调试模式)或崩溃。

答案 3 :(得分:1)

替代方案可能是有一个专用线程用于与一些FIFO队列的ZeroMQ通信(当然必须用互斥锁或类似的保护......)。只要队列为空,该专用线程就应该处于休眠状态,并在此状态发生变化时唤醒(正确发信号通知)。

根据一般需要,每当收到某些传出消息的响应时,专用线程可以简单地调用一些回调(在每个线程的某个专用对象上);请注意,您有不同的线程上下文,因此可能需要一些同步方法来防止竞争条件。

或者,发送线程可以等待响应,由ZeroMQ线程在收到的响应上发出信号(好吧,这实际上 是防止竞争条件的其中一种方法......)。