ZeroMQ:如何使用 inproc 减少多线程通信延迟?
ZeroMQ: how to reduce multithread-communication latency with inproc?
我正在使用 inproc
和 PAIR
来实现线程间通信,并试图解决由于轮询引起的延迟问题。如果我错了请纠正我:轮询是不可避免的,因为普通的 recv()
调用通常会阻塞并且不能采取特定的超时。
在我当前的例子中,在 N 个线程中,每个 N-1
工作线程都有一个主 while 循环。第 N 个线程是控制器线程,它会随时通知所有工作线程退出。但是,工作线程必须使用超时轮询来获取 quit
消息。这就引入了一个延迟,延迟参数通常是1000ms
。
这是一个例子
while (true) {
const std::chrono::milliseconds nTimeoutMs(1000);
std::vector<zmq::poller_event<std::size_t>> events(n);
size_t nEvents = m_poller.wait_all(events, nTimeoutMs);
bool isToQuit = false;
for (auto& evt : events) {
zmq::message_t out_recved;
try {
evt.socket.recv(out_recved, zmq::recv_flags::dontwait);
}
catch (std::exception& e) {
trace("{}: Caught exception while polling: {}. Skipped.", GetLogTitle(), e.what());
continue;
}
if (!out_recved.empty()) {
if (IsToQuit(out_recved))
isToQuit = true;
break;
}
}
if (isToQuit)
break;
//
// main business
//
...
}
更糟糕的是,当主循环有嵌套循环时,工作线程需要在嵌套循环的每一层中包含更多的轮询代码。很丑。
之所以选择ZMQ进行多线程通信,是因为它的优雅和摆脱线程锁定的潜力。但是我从来没有意识到轮询开销。
使用常规互斥锁或 std::atomic
数据操作时,我能否达到典型的延迟?我是否应该理解 inproc
实际上是一种伪装的网络通信模式,因此一些延迟是不可避免的?
上面发表的声明(一个假设):
"...a plain recv()
call will usually block and cannot take a specific timeout."
不正确:
一个普通的 .recv( ZMQ_NOBLOCK )
-call 永远不会 "block",
可以修饰一个普通的 .recv( ZMQ_NOBLOCK )
-call 以模仿 "a specific timeout"
上面发表的声明(假设):
"...have to use polling with a timeout ... introduces a latency, the latency parameter is usually 1000ms."
不正确:
- 不需要使用超时轮询
- 少一个不需要设置 1000 ms code-"injected"-latency,显然只花在-no-new-message state
Q : "Am I able to achieve the typical latency when using a regular mutex
or an std::atomic
data operation?"
是的。
Q : "Should I understand that the inproc
is in fact a network communication pattern in disguise so that some latency is inevitable?"
没有。 inproc
-transport-class 是所有这些类型中最快的,因为它主要是无协议/无堆栈的,并且与最终的快速指针有更多关系-机制,就像在双端环形缓冲区指针管理中一样。
最佳下一步:
1 )
重构您的代码,以便始终利用零等待 { .poll() | .recv() }
-方法,为两个{事件- |无事件- }-特定循环。
2)
如果那么愿意从智能环路检测周转时间中削减最后几个[us]
,可能会专注于改进Context()
-实例将其设置为使用更大数量的 nIOthreads > N
"under the hood".
可选 3 )
对于几乎硬实时系统的设计,人们可能 最终 利用确定性驱动的 Context()
线程和这些执行载体到特定的、非重叠 CPU-核心(使用精心制作的亲和图)
在代码中设置了 1000 [ms] 后,没有人会抱怨花费这 1000 [ms] 等待超时,由她/他自己编写代码。没有理由这样做。
不要将行为归咎于 ZeroMQ,它是从 API 的应用程序端编码的。
从来没有。
我正在使用 inproc
和 PAIR
来实现线程间通信,并试图解决由于轮询引起的延迟问题。如果我错了请纠正我:轮询是不可避免的,因为普通的 recv()
调用通常会阻塞并且不能采取特定的超时。
在我当前的例子中,在 N 个线程中,每个 N-1
工作线程都有一个主 while 循环。第 N 个线程是控制器线程,它会随时通知所有工作线程退出。但是,工作线程必须使用超时轮询来获取 quit
消息。这就引入了一个延迟,延迟参数通常是1000ms
。
这是一个例子
while (true) {
const std::chrono::milliseconds nTimeoutMs(1000);
std::vector<zmq::poller_event<std::size_t>> events(n);
size_t nEvents = m_poller.wait_all(events, nTimeoutMs);
bool isToQuit = false;
for (auto& evt : events) {
zmq::message_t out_recved;
try {
evt.socket.recv(out_recved, zmq::recv_flags::dontwait);
}
catch (std::exception& e) {
trace("{}: Caught exception while polling: {}. Skipped.", GetLogTitle(), e.what());
continue;
}
if (!out_recved.empty()) {
if (IsToQuit(out_recved))
isToQuit = true;
break;
}
}
if (isToQuit)
break;
//
// main business
//
...
}
更糟糕的是,当主循环有嵌套循环时,工作线程需要在嵌套循环的每一层中包含更多的轮询代码。很丑。
之所以选择ZMQ进行多线程通信,是因为它的优雅和摆脱线程锁定的潜力。但是我从来没有意识到轮询开销。
使用常规互斥锁或 std::atomic
数据操作时,我能否达到典型的延迟?我是否应该理解 inproc
实际上是一种伪装的网络通信模式,因此一些延迟是不可避免的?
上面发表的声明(一个假设):
"...a plain
recv()
call will usually block and cannot take a specific timeout."
不正确:
一个普通的 .recv( ZMQ_NOBLOCK )
-call 永远不会 "block",
可以修饰一个普通的 .recv( ZMQ_NOBLOCK )
-call 以模仿 "a specific timeout"
上面发表的声明(假设):
"...have to use polling with a timeout ... introduces a latency, the latency parameter is usually 1000ms."
不正确:
- 不需要使用超时轮询
- 少一个不需要设置 1000 ms code-"injected"-latency,显然只花在-no-new-message state
Q : "Am I able to achieve the typical latency when using a regular
mutex
or anstd::atomic
data operation?"
是的。
Q : "Should I understand that the
inproc
is in fact a network communication pattern in disguise so that some latency is inevitable?"
没有。 inproc
-transport-class 是所有这些类型中最快的,因为它主要是无协议/无堆栈的,并且与最终的快速指针有更多关系-机制,就像在双端环形缓冲区指针管理中一样。
最佳下一步:
1 )
重构您的代码,以便始终利用零等待 { .poll() | .recv() }
-方法,为两个{事件- |无事件- }-特定循环。
2)
如果那么愿意从智能环路检测周转时间中削减最后几个[us]
,可能会专注于改进Context()
-实例将其设置为使用更大数量的 nIOthreads > N
"under the hood".
可选 3 )
对于几乎硬实时系统的设计,人们可能 最终 利用确定性驱动的 Context()
线程和这些执行载体到特定的、非重叠 CPU-核心(使用精心制作的亲和图)
在代码中设置了 1000 [ms] 后,没有人会抱怨花费这 1000 [ms] 等待超时,由她/他自己编写代码。没有理由这样做。
不要将行为归咎于 ZeroMQ,它是从 API 的应用程序端编码的。
从来没有。