Как я могу избежать многопоточности + оптимизатор == бесконечный цикл? - PullRequest
3 голосов
/ 30 декабря 2010

В обзоре кода сегодня я наткнулся на следующий фрагмент кода (слегка измененный для публикации):

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

Это первые несколько строк кода, которые выполняются вновая тема.В другом потоке, когда инициализация завершена, он устанавливает initialized в true.

Я знаю, что оптимизатор может превратить это в бесконечный цикл, но как лучше всего избежать этого?

  • volatile - считается вредным
  • вызовом функции isInitialized() вместо непосредственного использования переменной - будет ли это гарантировать барьер памяти?Что, если функция была объявлена ​​inline?

Есть ли другие варианты?

Редактировать:

Должен был упомянуть об этом раньше,но это переносимый код, который нужно запускать в Windows, Linux, Solaris и т. д. В основном мы используем Boost.Thread для нашей переносимой библиотеки потоков.

Ответы [ 5 ]

5 голосов
/ 30 декабря 2010

Вызов функции не поможет вообще;даже если функция не объявлена ​​inline, ее тело все еще может быть встроено (исключая что-то экстремальное, например, помещая вашу функцию isInitialized() в другую библиотеку и динамически связываясь с ней).mind:

  • Объявите initialized как атомарный флаг (в C ++ 0x вы можете использовать std::atomic_flag; в противном случае вы захотите обратиться к документации для вашей библиотеки потоковкак это сделать)

  • Использовать семафор;получить его в другом потоке и ждать его в этом потоке.

4 голосов
/ 30 декабря 2010

@ Комментарий Карла - ответ.Не начинайте обработку в потоке A, пока поток B не завершит инициализацию.Для этого они посылают сигнал из потока B в поток A о том, что он запущен и работает.

Вы не упомянули ОС, поэтому я дам вам псевдокод Windows-ish.Перекодируйте в выбранную вами ОС / библиотеку.

Сначала создайте объект Windows Event.Это будет использоваться в качестве сигнала:

Нить 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, дождитесь, пока событие не сообщит:событие:

Тема 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
}
3 голосов
/ 30 декабря 2010

Примитивы синхронизации являются решением этой проблемы, а не вращаются в цикле ... Но если вы должны вращаться в цикле и не можете использовать семафор, событие и т. Д., Вы можете безопасно использовать volatile. Это считается вредным, потому что это вредит оптимизатору. В этом случае это именно то, что вы хотите сделать, нет?

0 голосов
/ 30 декабря 2010

Это хорошо известная проблема при работе с потоками.Создание / инициализация объектов занимает относительно мало времени.Когда поток фактически начинает работать, хотя ... Это может занять довольно много времени с точки зрения исполняемого кода.

Все продолжают упоминать семафоры ...

Возможно, вы захотите взглянуть на POSIX 1003.1б семафоры.В Linux попробуйте man sem_init .Например:

Преимущество этих семафоров в том, что после их создания/ Инициализированный, один поток может блокироваться на неопределенный срок, пока другой поток не сообщит об этом.Более того, этот сигнал может появиться ДО того, как ожидающий поток начнет ждать.(Значительная разница между семафорами и переменными состояния .) Кроме того, они могут обрабатывать ситуацию, когда вы получаете несколько сигналов перед пробуждением.

0 голосов
/ 30 декабря 2010

Существует импульсный эквивалент atomic_flag, который называется boost_flag в boost :: Once. Это вполне может быть то, что вы хотите здесь.

Фактически, если вы хотите, чтобы что-то создавалось при первом вызове, например, отложенная загрузка, и происходит в нескольких потоках, вы получаете boost :: once для вызова вашей функции при первом достижении. Постусловием является то, что оно было инициализировано, поэтому нет необходимости в каких-либо циклах или блокировках.

Вам нужно убедиться, что ваша логика инициализации не генерирует исключения.

...