一位读者。一位作家。关于互斥体和原子内置函数的一些常见问题
我有一个父线程和一个共享 bool 标志和 std::vector 的工作线程。父级仅读取(即读取 bool 或调用 my_vector.empty());工人只写。
我的问题:
我需要互斥保护布尔标志吗?
我可以说所有布尔读/写本质上都是原子操作吗?如果您回答“是”或“否”,您从哪里获得信息?
我最近听说了GCC Atomic-builtin。我可以使用它们使我的标志读/写原子化而不必使用互斥体吗?有什么区别?我知道原子内置函数可以归结为机器代码,但即使互斥体也可以归结为 CPU 的内存屏障指令,对吧?为什么人们称互斥体为“操作系统级”构造?
我需要互斥保护我的 std::vector 吗?回想一下,工作线程填充此向量,而父线程仅对其调用empty()(即,仅读取它)
我不认为布尔值或向量都需要互斥保护。我合理化如下:“好吧,如果我在更新之前读取共享内存......那也没关系,我下次会得到更新的值。更重要的是,我不明白为什么编写器应该被阻止阅读就是阅读,因为毕竟,读者只是阅读!”
如果有人能指出我正确的方向,那就太好了。我使用的是 GCC 4.3 和 Intel x86 32 位。 多谢!
I have a parent and a worker thread that share a bool flag and a std::vector. The parent only reads (i.e., reads the bool or calls my_vector.empty()); the worker only writes.
My questions:
Do I need to mutex protect the bool flag?
Can I say that all bool read/writes are inherently atomic operations? If you say Yes or No, where did you get your information from?
I recently heard about GCC Atomic-builtin. Can I use these to make my flag read/writes atomic without having to use mutexes? What is the difference? I understand Atomic builtins boil down to machine code, but even mutexes boil down to CPU's memory barrier instructions right? Why do people call mutexes an "OS-level" construct?
Do I need to mutex protect my std::vector? Recall that the worker thread populates this vector, whereas the parent only calls empty() on it (i.e., only reads it)
I do not believe mutex protection is necessary for either the bool or the vector. I rationalize as follows, "Ok, if I read the shared memory just before it was updated.. thats still fine, I will get the updated value the next time around. More importantly, I do not see why the writer should be blocked while the reading is reading, because afterall, the reader is only reading!"
If someone can point me in the right direction, that would be just great. I am on GCC 4.3, and Intel x86 32-bit.
Thanks a lot!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
不一定,原子指令就可以。我所说的“原子指令”是指编译器内部函数,a)防止编译器重新排序/优化,b)导致原子读/写,c)发出适当的内存栅栏以确保 CPU 之间的可见性(对于当前的 x86 CPU 采用 MESI 缓存一致性协议)。类似于 gcc 原子内置函数。
取决于CPU。对于英特尔 CPU - 是的。请参阅英特尔® 64 和 IA-32 架构软件开发人员手册。
原子和互斥体的区别在于,后者可以让等待线程休眠,直到互斥体被释放。对于原子,你只能忙自旋。
。
根据实现的不同,
vector.empty()
可能涉及读取两个缓冲区开始/结束指针并减去或比较它们,因此您有可能读取一个指针的新版本和旧版本另一个没有互斥体的。可能会出现令人惊讶的行为。Not necessarily, an atomic instruction would do. By
atomic instruction
I mean a compiler intrinsic function that a) prevents compiler reordering/optimization and b) results in atomic read/write and c) issues an appropriate memory fence to ensure visibility between CPUs (not necessary for current x86 CPUs which employ MESI cache coherency protocol). Similar to gcc atomic builtins.Depends on the CPU. For Intel CPUs - yes. See Intel® 64 and IA-32 Architectures Software Developer's Manuals.
The difference between atomics and mutexes is that the latter can put the waiting thread to sleep until the mutex is released. With atomics you can only busy-spin.
You do.
Depending on the implementation,
vector.empty()
may involve reading two buffer begin/end pointers and subtracting or comparing them, hence there is a chance that you read a new version of one pointer and an old version of another one without a mutex. Surprising behaviour may ensue.从 C++11 标准的角度来看,您必须使用互斥锁来保护 bool,或者使用
std::atomic
。即使您确定您的 bool 无论如何都会以原子方式读取和写入,编译器仍然有可能优化对其的访问,因为它不知道可能访问它的其他线程。如果由于某种原因您绝对需要平台的最新性能,请考虑阅读“Intel 64 和 IA-32 架构软件开发人员手册”,它将告诉您架构的幕后工作原理。但是,当然,这将使您的程序不可移植。
From the C++11 standards point of view, you have to protect the bool with a mutex, or alternatively use
std::atomic<bool>
. Even when you are sure that your bool is read and written to atomically anyways, there is still the chance that the compiler can optimize away accesses to it because it does not know about other threads that could potentially access it.If for some reason you absolutely need the latest bit of performance of your platform, consider reading the "Intel 64 and IA-32 Architectures Software Developer's Manual", which will tell you how things work under the hood on your architecture. But of course, this will make your program unportable.
答案:
在多线程环境中的上述代码中,线程可以在 if 语句(读取)和赋值(写入)之间被抢占,这意味着两个< /em> (或更多)具有此类代码的线程同时“拥有”互斥体(以及向量的权利)。这就是原子操作至关重要的原因:它们确保在上述场景中一次只能由一个线程设置标志,从而确保一次只能由一个线程操作向量。
请注意,将标志设置回 false 不一定是原子操作,因为您的此实例是唯一有权修改它的实例。
一个粗略的(阅读:未经测试的)解决方案可能看起来像:
原子内置的文档如下:
这意味着该操作将检查 flag 是否为 false 以及是否将其值设置为 true。如果值为 false,则返回 true,否则返回 false。所有这些都发生在一个原子步骤中,因此保证不会被另一个线程抢占。
Answers:
In the above code in a multithreaded environment it is possible for the thread to be preempted between the
if
statement (the read) and the assignment (the write), which means it possible for two (or more) threads with this kind of code to both "own" the mutex (and the rights to the vector) at the same time. This is why atomic operations are crucial: they ensure that in the above scenario the flag will only be set by one thread at a time, therefore ensuring the vector will only be manipulated by one thread at a time.Note that setting the flag back to false need not be an atomic operation because you this instance is the only one with rights to modify it.
A rough (read: untested) solution may look something like:
The documentation for the atomic builtin reads:
Which means the operation will check to see if flag is false and if it is set the value to true. If the value was false true is returned, otherwise false. All of this happens in an atomic step, so it is guaranteed not to be preempted by another thread.
我没有专业知识来回答您的整个问题,但在默认情况下读取非原子的情况下,您的最后一个项目符号是不正确的。
上下文切换可以在任何地方发生,读取器可以在读取过程中进行上下文切换,写入器可以切换并进行完整的写入,然后读取器将完成读取。读者既不会看到第一个值,也不会看到第二个值,但可能会看到一些非常不准确的中间值。
I don't have the expertise to answer your entire question but your last bullet is incorrect in cases in which reads are non-atomic by default.
A context switch can happen anywhere, the reader can get context switched partway through a read, the writer can get switched in and do the full write, and then the reader would finish their read. The reader would see neither the first value, nor the second value, but potentially some wildly inaccurate intermediate value.