Страницы

Поиск по вопросам

четверг, 29 ноября 2018 г.

C++ реализация call_once

Хочу разобраться в том, как работает std::call_once. И главное - lock-free ли он. Здесь пытаются его реализовать с использованием мьютекса. Если call_once можно реализовать только с использованием мьютекса, какие проблемы могут возникнуть с этим кодом?
#include #include #include
using namespace std; using my_once_flag = atomic;
void my_call_once(my_once_flag& flag, std::function foo) { bool expected = false; bool res = flag.compare_exchange_strong(expected, true, std::memory_order_release, std::memory_order_relaxed); if(res) foo(); } my_once_flag flag; void printOnce() { my_call_once(flag, [](){ cout << "test" << endl; }); } int main() { for(int i = 0; i< 50; ++i){ thread([](){ printOnce(); }).detach(); } return 0; }


Ответ

Стандарт не налагает ограничений на реализацию std::call_once поэтому, его реализация может быть как с блокировками, так и без(я не знаю, возможно ли такую реализацию придумать).
Что касается Вашей реализации: она просто неверна. Пусть у нас будет 2 потока, которые одновременно заходят в функцию и попадают на строчку: flag.compare_exchange_strong один из них выставит флаг, а другой уйдёт с полной уверенностью, что функция уже была вызвана. Но до вызова функции дело ещё вообще не дошло! Поэтому, в правильной реализации, на входе в call_once все потоки должны выстроиться в очередь, если кто-то уже начал выполнение функции.
Конечно, если бы функция была «чистой»(pure) можно было бы устроить спекулятивное выполнение, с принятием результата от того потока, что первым закончит её исполнение. Но стандарт не налагает никаких ограничений на функцию, которая может быть исполнена в call_once. Поэтому, в целом, я не вижу как можно её реализовать в неблокирующем виде.

Комментариев нет:

Отправить комментарий