我正在尝试使用 ZeroMQ (zmq) 来实现多线程程序。 我想避免复制需要传递给工作人员的数据。 我的想法是将指向数据的指针作为 zmq 消息发送。 有用。但我发现,如果我更改工作人员内部的数据,生产者(主要)不会看到它。我想知道为什么!
这是一个例子:
#include <cstring>
#include <iostream>
#include <memory>
#include <thread>
#include <zmq.h>
struct Foo {
explicit Foo(int value) noexcept
: _value{value}
{}
int _value;
};
void worker(void *context)
{
void *receiver = zmq_socket(context, ZMQ_REP);
zmq_connect(receiver, "inproc://example");
std::this_thread::sleep_for(std::chrono::milliseconds(20));
zmq_msg_t message;
zmq_msg_init(&message);
zmq_msg_recv(&message, receiver, 0);
char received[sizeof(Foo *)];
std::memcpy(&received, zmq_msg_data(&message), sizeof(Foo *));
//reinterpret_cast<Foo *>(received)->_value = 111;
std::cout << "Received Foo with value: " << reinterpret_cast<Foo *>(received)->_value << std::endl;
std::cout << " the address of value: " << std::addressof(reinterpret_cast<Foo *>(received)->_value) << std::endl;
zmq_msg_close(&message);
zmq_close(receiver);
}
int main()
{
void *context = zmq_ctx_new();
void *sender = zmq_socket(context, ZMQ_REQ);
zmq_bind(sender, "inproc://example");
std::jthread thread{worker, context};
auto foo{ std::make_unique<Foo>(42) };
std::cout << "Sending Foo with value : " << foo->_value << std::endl;
std::cout << " the address of value: " << std::addressof(foo->_value) << std::endl;
zmq_msg_t message;
zmq_msg_init_size(&message, sizeof(Foo *));
std::memcpy(zmq_msg_data(&message), foo.get(), sizeof(Foo *));
zmq_msg_send(&message, sender, 0);
zmq_msg_close(&message);
zmq_close(sender);
zmq_ctx_destroy(context);
return 0;
}
我得到的输出是
Sending Foo with value : 42
the address of value: 0x55a8c6fe2a90
Received Foo with value: 42
the address of value: 0x7f30c0a52d40
正如你所看到的,
_value
的地址是不同的,这就解释了为什么改变worker内部的值不会影响生产者。但我不明白为什么它们不同。
最后,这是否是发送数据而不通过
inproc
传输复制数据的正确方法?
Q2:
“(...)这是否是发送数据而不通过传输复制数据的正确方法?”inproc
好吧,
在 ZeroMQ Zen-of-Zero 中,其中包括保证接收原始版本的二进制精确副本,或者根本不接收任何内容(保证内容一致性,而不是交付),
是的。
剩下的就在你的设计中了。
inproc
传输被称为最快(零复制、内存指向)、最小开销、ZeroMQ 工具箱内可用的传输。
Q1 :
“我想知道为什么!(?)”
您的“外部”代码没有任何线索,您不仅移动指针,而且还生成(在高度优化编译器的“视线”之外)底层(仅弱引用)值的更改。
出现冲突并不奇怪,因为编译器从源代码中没有任何线索,这将在它返回“后面”发生。
可以尝试检查和比较编译器处理(是的,阅读实际使用的汇编指令)公共变量声明与
volatile
标记的指定变量的不同方式,以获得一些想法,可以使用哪些防御策略来不让“发起者”依赖于(这里不够充分)优化编译器“假设”。
参考文档通常对这些“惊喜”非常清楚,如下所示:
21.2 易失性变量和字段
GNU C 编译器经常执行优化,消除写入或读取变量的需要。(...)
如果包含 foo 的内存与另一个程序共享,或者由硬件异步检查,则此类优化可以使沟通混乱。使用是预防它们的一种方法。volatile
在变量或字段声明中使用类型编写表示该值可能随时因程序控制之外的原因而被检查或更改。因此,程序必须以谨慎的方式执行,以确保与这些访问进行正确的交互(无论何时发生)。volatile