如何避免线程化 +优化器==无限循环?
在今天的代码审查中,我偶然发现了以下内容代码(为发布而稍加修改):
while (!initialized)
{
// The thread can start before the constructor has finished initializing the object.
// Can lead to strange behavior.
continue;
}
这是在新线程中运行的前几行代码。在另一个线程中,一旦初始化完成,它就会将 initialized
设置为 true
。
我知道优化器可以将其变成无限循环,但是避免这种情况的最佳方法是什么?
易失性
- 被认为是有害的- 调用isInitialized() 函数而不是直接使用变量 - 这会保证内存屏障吗?如果函数被声明为内联怎么办?
还有其他选择吗?
编辑:
应该早点提到这一点,但这是需要在 Windows、Linux、Solaris 等上运行的可移植代码。我们主要使用 Boost.Thread 用于我们的便携式线程库。
In a code review today, I stumbled across the following bit of code (slightly modified for posting):
while (!initialized)
{
// The thread can start before the constructor has finished initializing the object.
// Can lead to strange behavior.
continue;
}
This is the first few lines of code that runs in a new thread. In another thread, once initialization is complete, it sets initialized
to true
.
I know that the optimizer could turn this into an infinite loop, but what's the best way to avoid that?
volatile
- considered harmful- calling an
isInitialized()
function instead of using the variable directly - would this guarantee a memory barrier? What if the function was declaredinline
?
Are there other options?
Edit:
Should have mentioned this sooner, but this is portable code that needs to run on Windows, Linux, Solaris, etc. We use mostly use Boost.Thread for our portable threading library.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
调用函数根本没有帮助;即使函数未声明为内联,其主体仍然可以内联(除非出现极端情况,例如将您的 isInitialized() 函数放入另一个库中并动态链接到它) 。
我想到了两个选项:
将
initialized
声明为原子标志(在 C++0x 中,您可以使用std::atomic_flag
;否则,您将想要查阅线程库的文档以了解如何执行此操作)使用信号量;在另一个线程中获取它并在本线程中等待它。
Calling a function won't help at all; even if a function is not declared
inline
, its body can still be inlined (barring something extreme, like putting yourisInitialized()
function in another library and dynamically linking against it).Two options that come to mind:
Declare
initialized
as an atomic flag (in C++0x, you can usestd::atomic_flag
; otherwise, you'll want to consult the documentation for your threading library for how to do this)Use a semaphore; acquire it in the other thread and wait for it in this thread.
@Karl 的评论就是答案。在线程 B 完成初始化之前,不要在线程 A 中开始处理。这样做的关键是从线程 B 向线程 A 发送一个信号,表明它已启动并正在运行。跑步。
你提到没有操作系统,所以我会给你一些 Windows 风格的伪代码。转码到您选择的操作系统/库。
首先创建一个 Windows 事件对象。这将用作信号:
线程 A:
然后让线程 A 启动线程 B,并将事件传递给它:
线程 A:
现在在线程 A 中,等待事件发出信号:
线程 A:
线程 B 的启动例程执行其任务初始化,然后发出事件信号:
线程 B:
@Karl's comment is the answer. Don't start processing in thread A until thread B has finished initialization. They key to doing this is sending a signal from thread B to thread A that it is up & running.
You mentioned no OS, so I will give you some Windows-ish psudocode. Transcode to the OS/library of your choice.
First create a Windows Event object. This will be used as the signal:
Thread A:
Then have Thread A start Thread B, passing the event along to it:
Thread A:
Now in Thread A, wait until the event is signaled:
Thread A:
Thread B's startup routine does its initialization, and then signals the event:
Thread B:
同步原语是这个问题的解决方案,而不是在循环中旋转...但是如果您必须在循环中旋转并且不能使用信号量、事件等,那么您可以安全地使用
易失性
。它被认为是有害的,因为它会损害优化器。在这种情况下,这正是您想做的,不是吗?Synchronization primitives are the solution to this problem, not spinning in a loop... But if you must spin in a loop and can't use a semaphore, event, etc, you can safely use
volatile
. It's considered harmful because it hurts the optimizer. In this case that's exactly what you want to do, no?有一个相当于atomic_flag的boost,在boost::once中称为once_flag。这很可能就是您想要的。
实际上,如果您希望在第一次调用时构造某些内容(例如延迟加载),并且发生在多个线程中,那么您可以在第一次到达函数时使用 boost::once 来调用该函数。后置条件是它已被初始化,因此不需要任何类型的循环或锁定。
您需要确保您的初始化逻辑不会抛出异常。
There is a boost equivalent of atomic_flag which is called once_flag in boost::once. It may well be what you want here.
Effectively if you want something to be constructed the first time it is called, eg lazy loading, and happens in multiple threads, you get boost::once to call your function the first time it is reached. The post-condition is that it has been initialized so there is no need for any kind of looping or locking.
What you do need to ensure is that your initialization logic does not throw exceptions.
这是使用线程时的一个众所周知的问题。对象的创建/初始化花费的时间相对较少。然而,当线程实际开始运行时......就执行的代码而言,这可能需要相当长的时间。
每个人都不断提到信号量...
您可能想看看 POSIX 1003.1b 信号量。在 Linux 下,尝试 man sem_init。例如:
这些信号量的优点是,一旦创建/初始化,一个线程就可以阻塞无限期地直到另一个线程发出信号为止。更重要的是,该信号可以在等待线程开始等待之前发生。 (信号量和条件变量之间存在显着差异。)此外,它们还可以处理您在醒来之前收到多个信号的情况。
This is a well known problem when working with threads. Creation/Initialization of objects takes relatively little time. When the thread actually starts running though... That can take quite a long time in terms of executed code.
Everyone keeps mentioning semaphores...
You may want to look at POSIX 1003.1b semaphores. Under Linux, try man sem_init. E.g.:
These semaphores have the advantage that, once Created/Initialized, one thread can block indefinitely until signaled by another thread. More critically, that signal can occur BEFORE the waiting thread starts waiting. (A significant difference between Semaphores and Condition Variables.) Also, they can handle the situation where you receive multiple signals before waking up.