2014-01-17 2 views
3

Предположим, что у вас был полиморфный тип Singleton (в нашем случае пользовательский тип std::error_category). Тип не имеет состояния, поэтому нет элементов данных, но у него есть пара виртуальных функций. Проблема возникает при создании экземпляра этого типа в многопоточной среде.Инициализация пустого полиморфного типа Singleton без магической статики

Самый простой способ достижения этой цели будет использовать 11 C++ magic statics-х:

my_type const& instantiate() { 
    static const my_type instance; 
    return instance; 
} 

К сожалению, один из наших компиляторов (VC11) не поддерживает эту функцию.

  • Должен ли я ожидать, что это взорвется в многопоточной среде? Я совершенно уверен, что, насколько стандарт идет, все ставки отключены. Но учитывая, что тип не содержит каких-либо членов данных и только виртуальных функций, какие ошибки я должен ожидать от реализации основного потока, например, VC11? Например, ни Boost.System, ни VC, похоже, не предпринимают никаких мер предосторожности при реализации error_category. Они просто небрежны или неоправданно параноичны, чтобы беспокоиться о гонках здесь?
  • Что было бы лучшим способом избавиться от гонки данных стандартным образом? Так как тип в этом случае равен error_category, я хочу, чтобы избежать выделения кучи, если это возможно. Имейте в виду, что семантика Singleton важна в этом случае, поскольку равенство категорий ошибок определяется путем сравнения указателей. Это означает, что, например, нить-локальное хранилище не является вариантом.

ответ

0

В стандарте не говорится о том, как статична конструкция, когда функция вызывается для нескольких потоков.

gcc использует блокировки, чтобы повысить уровень статики по функциям потокобезопасным (может быть отключен флагом). Большинство (всех?) Версий Visual C++ не имеют потокобезопасной функции уровня статики.

Для обеспечения безопасности резьбы рекомендуется использовать блокировку вокруг объявления переменной.

+1

C++ 11 [stmt.dcl]/4 не молчит:»... такая переменная является инициализируется в первый раз проходит через контроль его декларации; такая переменная считается инициализируется после завершения его инициализации. Если инициализация завершается путем исключения исключения, инициализация не завершена, поэтому она будет снова проверена в следующий раз, когда элемент управления войдет в объявление. Если элемент управления вводит объявление одновременно при инициализации переменной, одновременная выполнение должно ждать завершения ». – Casey

1

Попытка # 2b: Реализовать свой собственный эквивалент std::once_flag, с atomic<int> (Live at Rextester):

my_type const& instantiate() { 
    static std::aligned_storage<sizeof(my_type), __alignof(my_type)>::type storage; 
    static std::atomic_int flag; 

    while (flag < 2) { 
     // all threads spin until the object is properly initialized 
     int expected = 0; 
     if (flag.compare_exchange_weak(expected, 1)) { 
      // only one thread succeeds at the compare_exchange. 
      try { 
       ::new (&storage) my_type; 
      } catch(...) { 
       // Initialization failed. Let another thread try. 
       flag = 0; 
       throw; 
      } 
      // Success! 
      if (!std::is_trivially_destructible<my_type>::value) { 
       std::atexit([] { 
        reinterpret_cast<my_type&>(storage).~my_type(); 
       }); 
      } 
      flag = 2; 
     } 
    } 

    return reinterpret_cast<my_type&>(storage); 
} 

Это зависит только от компилятора правильно нулевой инициализировать все статические объекты, срок хранения, а также использует нестандартное расширение __alignof(<type>) для правильного выравнивания storage, так как команда компилятора Microsoft не может быть обеспокоена, добавьте ключевое слово без двух символов подчеркивания.


Попытка # 1: Используйте std::call_once в сочетании с std::once_flag ( Live demo at Coliru):

my_type const& instantiate() { 
    struct empty {}; 
    union storage_t { 
     empty e; 
     my_type instance; 
     constexpr storage_t() : e{} {} 
     ~storage_t() {} 
    }; 

    static std::once_flag flag; 
    static storage_t storage; 

    std::call_once(flag, []{ 
     ::new (&storage.instance) my_type; 
     std::atexit([]{ 
      storage.instance.~my_type(); 
     }); 
    }); 

    return storage.instance; 
} 

конструктор по умолчанию для std::once_flag является constexpr, поэтому он гарантированно будет построен при постоянной инициализации. Я под впечатлением [править], что VC правильно выполняет постоянную инициализацию. EDIT: К сожалению, MSVC через VS12 по-прежнему не поддерживает constexpr, поэтому этот метод имеет некоторое неопределенное поведение. Я попробую еще раз.

+0

Немного больше кода, чем хотелось бы, но он достигает цели обеспечения безопасности потоков без экспорта каких-либо объектов вне определения функции или причастности к фиаско порядка статического инициализации. – Casey

+0

Вы должны использовать atomic_int, а не атомный , потому что есть конструктор по умолчанию в атоме , который устанавливает флаг в 0 во время выполнения функции. Если это происходит, когда предыдущий поток только установил его в 1, вы получите два потока, входящих в критический раздел. atomic_int все еще инициализируется до 0, но это происходит при запуске программы, когда условие гонки невозможно. – Ian

+0

@Ian Да, спасибо, я забыл о том, что MSVC не смог правильно реализовать тривиальную конструкцию по умолчанию для 'atomic '. – Casey

2

Вот, возможно, более простая версия ответа Кейси, в которой используется атомная спин-блокировка для защиты обычной статической декларации.

my_type const& instantiate() 
{ 
    static std::atomic_int flag; 
    while (flag != 2) 
    { 
    int expected = 0; 
    if (flag.compare_exchange_weak(expected, 1)) 
     break; 
    } 
    try 
    { 
    static my_type instance = whatever; // <--- normal static decl and init 

    flag = 2; 
    return instance; 
    } 
    catch (...) 
    { 
    flag = 0; 
    throw; 
    } 
} 

Этот код также легче превратить в три макроса для повторного использования, которые легко #defined ни к чему на платформах, которые поддерживают магические статику.

my_type const& instantiate() 
{ 
    MY_MAGIC_STATIC_PRE; 

    static my_type instance = whatever; // <--- normal static decl and init 

    MY_MAGIC_STATIC_POST; 

    return instance; 

    MY_MAGIC_STATIC_SCOPE_END; 
} 
+0

@ Casey - Спасибо, что заметили это. Я исправил это сейчас. – Ian

Смежные вопросы