一位读者。一位作家。关于互斥体和原子内置函数的一些常见问题

发布于 2024-11-29 17:47:28 字数 703 浏览 0 评论 0原文

我有一个父线程和一个共享 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

呆头 2024-12-06 17:47:28

我需要互斥保护布尔标志吗?

不一定,原子指令就可以。我所说的“原子指令”是指编译器内部函数,a)防止编译器重新排序/优化,b)导致原子读/写,c)发出适当的内存栅栏以确保 CPU 之间的可见性(对于当前的 x86 CPU 采用 MESI 缓存一致性协议)。类似于 gcc 原子内置函数

我可以说所有布尔读/写本质上都是原子操作吗?如果您回答“是”或“否”,您从哪里获得信息?

取决于CPU。对于英特尔 CPU - 是的。请参阅英特尔® 64 和 IA-32 架构软件开发人员手册

我最近听说了 GCC Atomic-builtin。我可以使用它们使我的标志读/写原子化而不必使用互斥体吗?有什么区别?我知道原子内置函数可以归结为机器代码,但即使互斥体也可以归结为 CPU 的内存屏障指令,对吗?为什么人们将互斥体称为“操作系统级”构造?

原子和互斥体的区别在于,后者可以让等待线程休眠,直到互斥体被释放。对于原子,你只能忙自旋。

我需要互斥保护我的 std::vector 吗?回想一下,工作线程填充此向量,而父线程仅对其调用empty()(即仅读取它)

我不认为互斥保护对于 bool 或向量来说是必要的。我合理化如下:“好吧,如果我在更新之前读取共享内存......那也没关系,我下次会得到更新的值。更重要的是,我不明白为什么编写器应该被阻止阅读就是阅读,因为归根结底,读者只是阅读!”

根据实现的不同,vector.empty() 可能涉及读取两个缓冲区开始/结束指针并减去或比较它们,因此您有可能读取一个指针的新版本和旧版本另一个没有互斥体的。可能会出现令人惊讶的行为。

Do I need to mutex protect the bool flag?

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.

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?

Depends on the CPU. For Intel CPUs - yes. See Intel® 64 and IA-32 Architectures Software Developer's Manuals.

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?

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.

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)

You do.

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!"

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.

沫离伤花 2024-12-06 17:47:28

从 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.

秉烛思 2024-12-06 17:47:28

答案:

  1. 您需要保护有可能同时被两个或多个线程操作的布尔值(或任何其他与此相关的变量)。您可以使用互斥体或通过原子操作布尔值来完成此操作。
  2. 布尔读取和布尔写入可能是原子操作,但两个顺序操作肯定不是(例如,读取然后写入)。稍后会详细介绍这一点。
  3. 原子内置函数为上述问题提供了解决方案:能够在不能被另一个线程中断的步骤中读写变量。这使得操作原子化。
  4. 如果您使用 bool 标志作为“互斥体”(即,只有将 bool 标志设置为 true 的线程才有权修改向量),那么就可以了。互斥由布尔值管理,只要您使用原子操作修改布尔值,就应该全部设置完毕。
  5. 为了回答这个问题,让我举一个例子:

 

bool              flag(false);
std::vector<char> my_vector;

while (true)
{
    if (flag == false) // check to see if the mutex is owned
    {
        flag = true; // obtain ownership of the flag (the mutex)

        // manipulate the vector

        flag = false; // release ownership of the flag
    }
}

在多线程环境中的上述代码中,线程可以在 if 语句(读取)和赋值(写入)之间被抢占,这意味着两个< /em> (或更多)具有此类代码的线程同时“拥有”互斥体(以及向量的权利)。这就是原子操作至关重要的原因:它们确保在上述场景中一次只能由一个线程设置标志,从而确保一次只能由一个线程操作向量。

请注意,将标志设置回 false 不一定是原子操作,因为您的此实例是唯一有权修改它的实例。

一个粗略的(阅读:未经测试的)解决方案可能看起来像:

bool              flag(false);
std::vector<char> my_vector;

while (true)
{
    // check to see if the mutex is owned and obtain ownership if possible
    if (__sync_bool_compare_and_swap(&flag, false, true)) 
    {
        // manipulate the vector

        flag = false; // release ownership of the flag
    }
}

原子内置的文档如下:

如果比较成功并且 newval 被写入,“bool”版本将返回 true。

这意味着该操作将检查 flag 是否为 false 以及是否将其值设置为 true。如果值为 false,则返回 true,否则返回 false。所有这些都发生在一个原子步骤中,因此保证不会被另一个线程抢占。

Answers:

  1. You will need to protect the bool (or any other variable for that matter) that has the possibility of being operated on by two or more threads at the same time. You can either do this with a mutex or by operating on the bool atomically.
  2. Bool reads and bool writes may be atomic operations, but two sequential operations are certainly not (e.g., a read and then a write). More on this later.
  3. Atomic builtins provide a solution to the problem above: the ability to read and write a variable in a step that cannot be interrupted by another thread. This makes the operation atomic.
  4. If you are using the bool flag as your 'mutex' (that is, only the thread that sets the bool flag to true has permission to modify the vector) then you're OK. The mutual exclusion is managed by the boolean, and as long as you're modifying the bool using atomic operations you should be all set.
  5. To answer this, let me use an example:

 

bool              flag(false);
std::vector<char> my_vector;

while (true)
{
    if (flag == false) // check to see if the mutex is owned
    {
        flag = true; // obtain ownership of the flag (the mutex)

        // manipulate the vector

        flag = false; // release ownership of the flag
    }
}

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:

bool              flag(false);
std::vector<char> my_vector;

while (true)
{
    // check to see if the mutex is owned and obtain ownership if possible
    if (__sync_bool_compare_and_swap(&flag, false, true)) 
    {
        // manipulate the vector

        flag = false; // release ownership of the flag
    }
}

The documentation for the atomic builtin reads:

The “bool” version returns true if the comparison is successful and newval was written.

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.

黑寡妇 2024-12-06 17:47:28

我没有专业知识来回答您的整个问题,但在默认情况下读取非原子的情况下,您的最后一个项目符号是不正确的。

上下文切换可以在任何地方发生,读取器可以在读取过程中进行上下文切换,写入器可以切换并进行完整的写入,然后读取器将完成读取。读者既不会看到第一个值,也不会看到第二个值,但可能会看到一些非常不准确的中间值。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文