C++ 多线程环境中的内存访问
Posted
技术标签:
【中文标题】C++ 多线程环境中的内存访问【英文标题】:Memory access in C++ multithreading environment 【发布时间】:2017-07-20 02:31:19 【问题描述】:我有一个问题,即主线程中的函数被阻塞,直到在另一个线程中设置了局部变量。我用信号量来阻塞主线程执行:
int sendRequest(Message request, void *data)
semaphore waitForReply;
volatile int result = 0;
volatile int retData[100];
client->sendRequest(request, [&result, &retData](const Message& reply) // Callback is called from a different thread
result = reply.result;
memcpy(retData, reply.retData, sizeof(retData));
waitForReply.signal();
)
waitForReply.wait();
//At this line we want result var to be updated.
memcpy(data, retData, sizeof(retData));
return result;
问题是使用 volatile int result 是否保证返回结果是从回调接收到的实际值?这是解决此问题的最佳方法还是使用普通变量和互斥锁更好?
数组 retData 的情况如何? (请不要介意数组的大小)
【问题讨论】:
不,volatile
in 是不够的。
相关:What's the difference of the usage of volatile between C/C++ and C#/Java?
谢谢,你知道如何解决这个问题吗?
您的代码在我看来是正确的。 volatile
没有做任何有用的事情,你应该删除它。您的回调仅在数据被复制出来后才会发出信号量,因此您可以保证在wait()
返回后获得正确的数据。或者,换句话说,不是volatile
保证了这里的正确行为。保证来自signal()
在数据复制后被调用,wait()
将阻塞并等待该信号。
@NikosC。这是我不确定的事情,如果我错了,请纠正我。如果当result = reply.result;时,result的新值还没有写入内存而是缓存在寄存器的某个地方,那么返回的结果(return result;)是旧值怎么办?
【参考方案1】:
Volatile
在 C 或 C++ 中对于多线程代码没有用处,因为它不会创建内存屏障。但是,您的代码中没有任何内容需要内存屏障,所以您已经可以了。
错误代码的一个例子是:
// initially
my_pointer = nullptr;
ready_flag = false;
// thread 1
my_pointer = some_pointer_here;
ready_flag = true;
// thread 2
while (!ready_flag) /* wait */;
my_pointer->foo = bar;
这段代码很危险,因为很多事情都可以使ready_flag
的写入在my_pointer
的写入之前变得可见,即使它在源代码中出现在第二位。在这种情况下,您可以在分配值之前访问my_pointer
。
内存屏障是一条指令,它强制执行特定顺序以使内存写入可见。例如,该代码在写入my_pointer
和写入ready_flag
之间需要一个内存屏障。 C++ 标准库为此提供了std::atomic_thread_fence
。此外,std::atomic
类型都可以生成内存屏障。
在您的情况下,您的变量没有相互依赖关系(除了整个操作必须已完成),而且信号量的 wait
方法几乎肯定有障碍。
在内核/CPU 之间实现缓存和内存一致性的确切方法取决于平台。 AFAIK,在 x86 和 ARM 衍生产品上,您根本不需要做任何事情。在更疯狂的平台上,这通常由内存栅栏来处理,无论如何你都会使用它。
【讨论】:
感谢您的回答,“信号量等待方法保证内存屏障”解决了我的问题。【参考方案2】:结果缓冲区中的数据是好的。在 lambda 中对 memcpy 的调用可确保在信号量 waitForSignal 发出信号之前完整复制数据。但是……
-
不,在这种情况下,volatile 关键字没有任何作用。 memcpy 不受 volatile 关键字的影响。
volatile
只影响代码的优化方式。
为什么不捕获data
并让lambda 直接填充该缓冲区?这样可以保存一份副本。
我没有看到任何说明输出缓冲区大小的参数data
。这不是很好的做法。我建议您考虑使用 std::vector 或其他一些安全的方式来实现缓冲区。
【讨论】:
我希望当代码经过这一行时(//在这一行我们要更新result var。),更新data[]和result的值。如果我捕获数据并对其进行 memcpy 操作,是否会在函数返回时 data[] 的值尚未更新?如果 volatile 无法保证这一点,可以采取什么措施来实现上述预期结果?请不要介意尺寸 (3),这只是一个示例。以上是关于C++ 多线程环境中的内存访问的主要内容,如果未能解决你的问题,请参考以下文章
C++ volatile关键字(多线程中声明为易变值不稳定值,告诉程序每次都从内存读取,不被编译优化,防止被优化后变量异常)