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

时间:2010-12-30 17:20:10

标签: c++ multithreading thread-safety compiler-optimization

今天在code review中,我偶然发现了以下一些代码(稍微修改后发布):

while (!initialized)
{
  // The thread can start before the constructor has finished initializing the object.
  // Can lead to strange behavior. 
  continue;
}

这是在新线程中运行的前几行代码。在另一个线程中,初始化完成后,它会将initialized设置为true

我知道优化器可以将其转换为无限循环,但是避免这种情况的最佳方法是什么?

  • volatile - considered harmful
  • 调用isInitialized()函数而不是直接使用变量 - 这会保证内存障碍吗?如果函数声明为inline
  • ,该怎么办?

还有其他选择吗?

编辑:

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

5 个答案:

答案 0 :(得分:5)

调用函数根本无济于事;即使一个函数没有被声明为inline,它的主体仍然可以被内联(除非是极端的东西,比如将你的isInitialized()函数放在另一个库中并动态链接它)。

可以想到两个选项:

  • initialized声明为原子标志(在C ++ 0x中,您可以使用std::atomic_flag;否则,您需要查阅线程库的文档以了解如何操作此)

  • 使用信号量;在另一个线程中获取它并在此线程中等待它。

答案 1 :(得分:4)

@ Karl的评论就是答案。在线程B完成初始化之前,不要在线程A中开始处理。他们这样做的关键是从线程B向线程A发送一个信号,它正在向上和向上发送。运行

你提到没有操作系统,所以我会给你一些Windows-ish psudocode。转码到您选择的操作系统/库。

首先创建一个Windows事件对象。这将用作信号:

线程A:

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

然后让线程A启动线程B,将事件传递给它:

线程A:

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

现在在线程A中,等到事件发出信号:

线程A:

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

线程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
}

答案 2 :(得分:3)

同步原语是这个问题的解决方案,而不是在循环中旋转......但是如果你必须在循环中旋转而不能使用信号量,事件等,你可以安全地使用volatile。它被认为是有害的,因为它会伤害优化器。在这种情况下,这正是你想要做的,不是吗?

答案 3 :(得分:0)

有一个相当于atomic_flag的提升,在boost :: once中称为once_flag。这可能是你想要的。

实际上,如果你想在第一次调用它时构造一些东西,例如延迟加载,并且在多个线程中发生,你可以在第一次调用函数时获得boost :: once。后置条件是它已被初始化,因此不需要任何类型的循环或锁定。

您需要确保的是您的初始化逻辑不会抛出异常。

答案 4 :(得分:0)

使用线程时这是一个众所周知的问题。对象的创建/初始化需要相对较少的时间。当线程实际开始运行时......就执行的代码而言,这可能需要相当长的时间。

每个人都不断提到信号量......

您可能需要查看POSIX 1003.1b信号量。在Linux下,尝试 man sem_init 。 E.g:

这些信号量的优点是,一旦创建/初始化,一个线程可以无限期地阻塞,直到另一个线程发出信号。更重要的是,该信号可以在等待线程开始等待之前发生。 (信号量条件变量之间的显着差异。)此外,它们可以处理在唤醒之前收到多个信号的情况。