简体   繁体   English

如何避免线程+优化器==无限循环?

[英]How can I avoid threading + optimizer == infinite loop?

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 . 在另一个线程中,一旦初始化完成,它将initialized设置为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 volatile - 被认为有害
  • calling an isInitialized() function instead of using the variable directly - would this guarantee a memory barrier? 调用isInitialized()函数而不是直接使用变量 - 这会保证内存屏障吗? What if the function was declared inline ? 如果函数是inline声明的,该怎么办?

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. 应该早点提到这一点,但这是需要在Windows,Linux,Solaris等上运行的可移植代码。我们主要使用Boost.Thread作为我们的便携式线程库。

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 your isInitialized() function in another library and dynamically linking against it). 即使一个函数没有inline声明,它的主体仍然可以内联(除非是极端的东西,比如将你的isInitialized()函数放在另一个库中并动态链接它)。

Two options that come to mind: 想到两个选项:

  • Declare initialized as an atomic flag (in C++0x, you can use std::atomic_flag ; otherwise, you'll want to consult the documentation for your threading library for how to do this) 声明initialized为原子标志(在C ++ 0x中,您可以使用std::atomic_flag ;否则,您将需要查阅线程库的文档以了解如何执行此操作)

  • Use a semaphore; 使用信号量; acquire it in the other thread and wait for it in this thread. 在另一个线程中获取它并在此线程中等待它。

@Karl's comment is the answer. @ Karl的评论就是答案。 Don't start processing in thread A until thread B has finished initialization. 在线程B完成初始化之前,不要在线程A中开始处理。 They key to doing this is sending a signal from thread B to thread A that it is up & running. 他们这样做的关键是从线程B向线程A发送一个信号,表明它正在运行。

You mentioned no OS, so I will give you some Windows-ish psudocode. 你提到没有操作系统,所以我会给你一些Windows-ish psudocode。 Transcode to the OS/library of your choice. 转码到您选择的操作系统/库。

First create a Windows Event object. 首先创建一个Windows事件对象。 This will be used as the signal: 这将用作信号:

Thread A: 线程A:

HANDLE running = CreateEvent(0, TRUE, FALSE, 0);

Then have Thread A start Thread B, passing the event along to it: 然后让线程A启动线程B,将事件传递给它:

Thread A: 线程A:

DWORD thread_b_id = 0;
HANDLE thread_b = CreateThread(0, 0, ThreadBMain, (void*)handle, 0, &thread_b_id);

Now in Thread A, wait until the event is signaled: 现在在线程A中,等待事件发出信号:

Thread A: 线程A:

DWORD rc = WaitForSingleObject(running, INFINITE);
if( rc == WAIT_OBJECT_0 )
{
  // thread B is up & running now...
  // MAGIC HAPPENS
}

Thread B's startup routine does its initialization, and then signals the event: 线程B的启动例程进行初始化,然后发出事件信号:

Thread B: 线程B:

DWORD WINAPI ThreadBMain(void* param)
{
  HANDLE running = (HANDLE)param;
  do_expensive_initialization();
  SetEvent(running); // this will tell Thread A that we're good to go
}

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 . 同步原语是这个问题的解决方案,而不是在循环中旋转......但是如果你必须在循环中旋转而不能使用信号量,事件等,你可以安全地使用volatile It's considered harmful because it hurts the optimizer. 它被认为是有害的,因为它会伤害优化器。 In this case that's exactly what you want to do, no? 在这种情况下,这正是你想要做的,不是吗?

There is a boost equivalent of atomic_flag which is called once_flag in boost::once. 有一个相当于atomic_flag的boost,在boost :: once中称为once_flag。 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. 有效地,如果你想在第一次调用时构造一些东西,例如延迟加载,并且在多个线程中发生,你可以在第一次调用函数时获得boost :: once。 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. 您需要确保的是您的初始化逻辑不会抛出异常。

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. 您可能想要查看POSIX 1003.1b信号量。 Under Linux, try man sem_init . 在Linux下,尝试man sem_init Eg: 例如:

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. 信号量条件变量之间的显着差异。)此外,它们可以处理在唤醒之前收到多个信号的情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM