В этом разделе представлены механизмы взаимного исключения: мьютексы, блокировки и однократный вызов. Эти механизмы упрощают создание программ, свободных от расы ([intro.multithread]).
namespace std { class mutex; class recursive_mutex; class timed_mutex; class recursive_timed_mutex; struct defer_lock_t { explicit defer_lock_t() = default; }; struct try_to_lock_t { explicit try_to_lock_t() = default; }; struct adopt_lock_t { explicit adopt_lock_t() = default; }; inline constexpr defer_lock_t defer_lock { }; inline constexpr try_to_lock_t try_to_lock { }; inline constexpr adopt_lock_t adopt_lock { }; template <class Mutex> class lock_guard; template <class... MutexTypes> class scoped_lock; template <class Mutex> class unique_lock; template <class Mutex> void swap(unique_lock<Mutex>& x, unique_lock<Mutex>& y) noexcept; template <class L1, class L2, class... L3> int try_lock(L1&, L2&, L3&...); template <class L1, class L2, class... L3> void lock(L1&, L2&, L3&...); struct once_flag; template<class Callable, class... Args> void call_once(once_flag& flag, Callable&& func, Args&&... args); }
Объект мьютекса обеспечивает защиту от гонок данных и обеспечивает безопасную синхронизацию данных между ними execution agents. Агент выполнения owns - мьютекс с момента успешного вызова одной из функций блокировки до вызова разблокировки. Мьютексы могут быть рекурсивными или нерекурсивными и могут предоставлять одновременное владение одному или нескольким агентам выполнения. Поставляются как рекурсивные, так и нерекурсивные мьютексы.
mutex types Стандартные библиотеки типов mutex, recursive_mutex, timed_mutex, recursive_timed_mutex, shared_mutex, и shared_timed_mutex. Они должны соответствовать требованиям, изложенным в этом разделе. В этом описании m обозначает объект типа мьютекса.
Типы мьютексов должны быть DefaultConstructible и Destructible. Если инициализация объекта типа мьютекса не удалась,system_error должно быть выброшено исключение типа . Типы мьютексов нельзя копировать или перемещать.
Условия ошибки для кодов ошибок, если таковые имеются, сообщаемые функциями-членами типов мьютексов, должны быть:
resource_unavailable_try_again - если какой-либо управляемый тип дескриптора недоступен.
operation_not_permitted - если поток не имеет права выполнять операцию.
invalid_argument - если какой-либо собственный тип дескриптора, манипулируемый как часть конструкции мьютекса, неверен.
Реализация должна обеспечивать операции блокировки и разблокировки, как описано ниже. В целях определения наличия гонки данных они ведут себя как атомарные операции ([intro.multithread]). Операции блокировки и разблокировки на одном мьютексе должны выполняться в едином общем порядке. [ Note: Это можно рассматривать как modification order мьютекс. ] [ Создание и уничтожение объекта типа мьютекса не обязательно должно быть потокобезопасным; следует использовать другую синхронизацию, чтобы гарантировать, что объекты мьютекса инициализированы и видны другим потокам. ] — end note Note: — end note
Requires: Если m имеет тип mutex, timed_mutex, shared_mutexили shared_timed_mutex, вызывающий поток не является владельцем мьютекса.
Effects: Блокирует вызывающий поток до тех пор, пока для вызывающего потока не будет получено право владения мьютексом.
Synchronization: Предыдущие unlock() операции на одном и том же объекте являются synchronize with этой операцией.
Throws: system_error когда требуется исключение ([thread.req.exception]).
Requires: Если m имеет тип mutex, timed_mutex, shared_mutexили shared_timed_mutex, вызывающий поток не является владельцем мьютекса.
Effects: Пытается получить право собственности на мьютекс для вызывающего потока без блокировки. Если право собственности не получено, нет никакого эффекта и try_lock() немедленно возвращается. Реализация может не получить блокировку, даже если она не удерживается каким-либо другим потоком. [ Note: Этот ложный сбой обычно встречается редко, но допускает интересные реализации, основанные на простом сравнении и обмене (пункт [atomics]). ] Реализация должна гарантировать, что не будет последовательного возврата в отсутствие конкурирующих захватов мьютексов. — end note try_lock() false
Returns: true если право собственности на мьютекс было получено для вызывающего потока, в противном случае false.
Synchronization: Если try_lock() возвращается true, предыдущие unlock() операции с тем же объектом synchronize with это операция. [ Note: Так lock() как не синхронизируется с неудачным последующим try_lock(), правила видимости достаточно слабы, чтобы о состоянии после отказа было бы мало что известно, даже при отсутствии ложных отказов. ] — end note
Synchronization: Эта операция является synchronizes with последующими операциями блокировки, которые получают право собственности на один и тот же объект.
namespace std { class mutex { public: constexpr mutex() noexcept; ~mutex(); mutex(const mutex&) = delete; mutex& operator=(const mutex&) = delete; void lock(); bool try_lock(); void unlock(); using native_handle_type = implementation-defined; // See [thread.req.native] native_handle_type native_handle(); // See [thread.req.native] }; }
Класс mutex предоставляет нерекурсивный мьютекс с семантикой исключительного владения. Если один поток владеет объектом мьютекса, попытки другого потока получить право владения этим объектом будут неудачными (для try_lock()) или блокируются (для lock()) до тех пор, пока поток-владелец не освободит владение с помощью вызова unlock().
[ Note: После того, как поток A вызвал unlock(), освободив мьютекс, другой поток B может заблокировать тот же мьютекс, заметить, что он больше не используется, разблокировать его и уничтожить до того, как поток, A кажется, вернется из своего вызова разблокировки. Для правильной обработки таких сценариев требуются реализации, если поток A не обращается к мьютексу после возврата вызова разблокировки. Эти случаи обычно возникают, когда объект со счетчиком ссылок содержит мьютекс, который используется для защиты счетчика ссылок. ] — end note
Класс mutex должен удовлетворять всем требованиям mutex requirements. Это должен быть standard-layout class.
[ Note: Программа может заблокироваться, если поток, которому принадлежит mutex объект, вызывает lock() этот объект. Если реализация может обнаружить тупик, resource_deadlock_would_occur может наблюдаться состояние ошибки. ] — end note
namespace std { class recursive_mutex { public: recursive_mutex(); ~recursive_mutex(); recursive_mutex(const recursive_mutex&) = delete; recursive_mutex& operator=(const recursive_mutex&) = delete; void lock(); bool try_lock() noexcept; void unlock(); using native_handle_type = implementation-defined; // See [thread.req.native] native_handle_type native_handle(); // See [thread.req.native] }; }
Класс recursive_mutex предоставляет рекурсивный мьютекс с семантикой исключительного владения. Если один поток владеет recursive_mutex объектом, попытки другого потока получить право владения этим объектом завершатся ошибкой (для try_lock()) или заблокируются (для lock()) до тех пор, пока первый поток полностью не освободит владение.
Класс recursive_mutex должен удовлетворять всем требованиям mutex requirements. Это должен быть standard-layout class.
Поток, которому принадлежит recursive_mutex объект, может получить дополнительные уровни владения путем вызова этого объекта lock() или try_lock() для этого объекта. Не указано, сколько уровней владения может быть получено одним потоком. Если поток уже получил максимальный уровень владения recursive_mutex объектом, дополнительные вызовы try_lock() должны завершиться ошибкой, а дополнительные вызовы lock() вызовут исключение типа system_error. Поток должен вызывать unlock() один раз для каждого уровня владения, полученного вызовами lock() и try_lock(). Только когда все уровни владения высвобождены, право владения может быть приобретено другим потоком.
timed mutex types Стандартные библиотеки типов timed_mutex, recursive_timed_mutexи shared_timed_mutex. Они должны соответствовать требованиям, изложенным ниже. В этом описании m обозначает объект типа мьютекса, rel_time обозначает объект создания экземпляра durationи abs_time обозначает объект создания экземпляра time_point.
Выражение m.try_lock_for(rel_time) должно быть правильно сформированным и иметь следующую семантику:
Requires: Если m имеет тип timed_mutex или shared_timed_mutex, вызывающий поток не владеет мьютексом.
Effects: Функция пытается получить право собственности на мьютекс в течение относительного времени ожидания ([thread.req.timing]), указанного в rel_time. Если время, указанное в, rel_time меньше или равно rel_time.zero(), функция пытается получить право владения без блокировки (как если бы путем вызова try_lock()). Функция должна вернуться в течение тайм-аута, указанного в, rel_time только если она получила право собственности на объект мьютекса. [ Note: Как и в случае try_lock(), нет никакой гарантии, что право собственности будет получено, если блокировка доступна, но ожидается, что реализации приложат для этого серьезные усилия. ] — end note
Synchronization: Если try_lock_for() возвращается true, предыдущие unlock() операции с тем же объектом synchronize with это операция.
Throws: Исключения, связанные с тайм-аутом ([thread.req.timing]).
Выражение m.try_lock_until(abs_time) должно быть правильно сформированным и иметь следующую семантику:
Requires: Если m имеет тип timed_mutex или shared_timed_mutex, вызывающий поток не владеет мьютексом.
Effects: Функция пытается получить право собственности на мьютекс. Если abs_time он уже прошел, функция пытается получить право собственности без блокировки (как если бы путем вызова try_lock()). Функция должна возвращаться до абсолютного тайм-аута ([thread.req.timing]), указанного в, abs_time только если она получила право собственности на объект мьютекса. [ Note: Как и в случае try_lock(), нет никакой гарантии, что право собственности будет получено, если блокировка доступна, но ожидается, что реализации приложат для этого серьезные усилия. ] — end note
Synchronization: Если try_lock_until() возвращается true, предыдущие unlock() операции с тем же объектом synchronize with это операция.
Throws: Исключения, связанные с тайм-аутом ([thread.req.timing]).
namespace std { class timed_mutex { public: timed_mutex(); ~timed_mutex(); timed_mutex(const timed_mutex&) = delete; timed_mutex& operator=(const timed_mutex&) = delete; void lock(); // blocking bool try_lock(); template <class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time); template <class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time); void unlock(); using native_handle_type = implementation-defined; // See [thread.req.native] native_handle_type native_handle(); // See [thread.req.native] }; }
Класс timed_mutex предоставляет нерекурсивный мьютекс с семантикой исключительного владения. Если один поток владеет timed_mutex объектом, попытки другого потока приобрести в собственность этого объекта не получится (для try_lock()) или блока (для lock(), try_lock_for()и try_lock_until()) до тех пор , владеющего нить не выпустило собственность с помощью вызова unlock() или вызова try_lock_for() или try_lock_until() тайма - аута (имеющего не удалось получить право собственности).
Класс timed_mutex должен удовлетворять всем требованиям timed mutex requirements. Это должен быть standard-layout class.
Поведение программы не определено, если:
он уничтожает timed_mutex объект, принадлежащий любому потоку,
поток , который владеет timed_mutex объект звонки lock(), try_lock(), try_lock_for()или try_lock_until() на этом объекте, или
поток завершается при владении timed_mutex объектом.
namespace std { class recursive_timed_mutex { public: recursive_timed_mutex(); ~recursive_timed_mutex(); recursive_timed_mutex(const recursive_timed_mutex&) = delete; recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete; void lock(); // blocking bool try_lock() noexcept; template <class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time); template <class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time); void unlock(); using native_handle_type = implementation-defined; // See [thread.req.native] native_handle_type native_handle(); // See [thread.req.native] }; }
Класс recursive_timed_mutex предоставляет рекурсивный мьютекс с семантикой исключительного владения. Если один поток владеет recursive_timed_mutex объектом, попытки другого потока приобрести в собственность этого объекта не получится (для try_lock()) или блок (для lock(), try_lock_for()и try_lock_until()) до тех пор , владеющее поток не полностью освобождается право собственности или вызов try_lock_for() или try_lock_until() тайм - аут (не добившись право собственности).
Класс recursive_timed_mutex должен удовлетворять всем требованиям timed mutex requirements. Это должен быть класс стандартной компоновки (пункт [class]).
Поток , который владеет recursive_timed_mutex объектом может приобрести дополнительные уровни владения вызывающего lock(), try_lock(), try_lock_for()или try_lock_until() на этом объект. Не указано, сколько уровней владения может быть получено одним потоком. Если поток уже приобрел максимальный уровень владения для recursive_timed_mutex объекта, дополнительные вызовов try_lock(), try_lock_for()или try_lock_until() ослабеют, а также дополнительные призывов lock() бросит исключение типа system_error. Поток наречет unlock() один раз для каждого уровня владения приобретаемого вызовов lock(), try_lock(), try_lock_for(), и try_lock_until(). Только когда все уровни владения были освобождены, владение объектом может быть приобретено другим потоком.
A lock - это объект, который содержит ссылку на блокируемый объект и может разблокировать блокируемый объект во время разрушения блокировки (например, при выходе из области действия блока). Агент выполнения может использовать блокировку, чтобы помочь в управлении владением блокируемым объектом безопасным способом. Блокировка называется own блокируемым объектом, если он в настоящее время управляет владением этим блокируемым объектом для агента выполнения. Блокировка не управляет временем существования блокируемого объекта, на который она ссылается. [ Note: Замки предназначены для облегчения бремени разблокировки запираемого объекта как в нормальных, так и в исключительных обстоятельствах. ] — end note
Некоторые конструкторы блокировок используют типы тегов, которые описывают, что следует делать с блокируемым объектом во время создания блокировки.
namespace std { struct defer_lock_t { }; // do not acquire ownership of the mutex struct try_to_lock_t { }; // try to acquire ownership of the mutex // without blocking struct adopt_lock_t { }; // assume the calling thread has already // obtained mutex ownership and manage it inline constexpr defer_lock_t defer_lock { }; inline constexpr try_to_lock_t try_to_lock { }; inline constexpr adopt_lock_t adopt_lock { }; }
namespace std {
template <class Mutex>
class lock_guard {
public:
using mutex_type = Mutex;
explicit lock_guard(mutex_type& m);
lock_guard(mutex_type& m, adopt_lock_t);
~lock_guard();
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
mutex_type& pm; // exposition only
};
template<class Mutex> lock_guard(lock_guard<Mutex>) -> lock_guard<Mutex>;
}
Объект типа lock_guard контролирует владение блокируемым объектом в области. lock_guard Объект сохраняет право собственности на блокируемый объект на протяжении всего lock_guard объекта , lifetime. Поведение программы не определено, если блокируемый объект, на который указывает ссылка, pm не существует в течение всего времени существования lock_guard объекта. Поставляемый Mutex тип должен соответствовать BasicLockable требованиям.
explicit lock_guard(mutex_type& m);
lock_guard(mutex_type& m, adopt_lock_t);
~lock_guard();
namespace std { template <class... MutexTypes> class scoped_lock { public: using mutex_type = Mutex; // If MutexTypes... consists of the single type Mutex explicit scoped_lock(MutexTypes&... m); explicit scoped_lock(MutexTypes&... m, adopt_lock_t); ~scoped_lock(); scoped_lock(const scoped_lock&) = delete; scoped_lock& operator=(const scoped_lock&) = delete; private: tuple<MutexTypes&...> pm; // exposition only }; template<class... MutexTypes> scoped_lock(scoped_lock<MutexTypes...>) -> scoped_lock<MutexTypes...>; }
Объект типа scoped_lock контролирует владение блокируемыми объектами в области. scoped_lock Объект сохраняет право собственности на запираемых объектов на всей территории scoped_lock объекта , lifetime. Поведение программы не определено, если блокируемые объекты, на которые указывает ссылка pm , не существуют в течение всего времени существования scoped_lock объекта. Когда sizeof...(MutexTypes) есть 1, поставляемый Mutex тип должен соответствовать BasicLockable требованиям. В противном случае каждый из типов мьютексов должен соответствовать Lockable требованиям.
explicit scoped_lock(MutexTypes&... m);
Requires: Если MutexTypes тип не является рекурсивным мьютексом, вызывающий поток не владеет соответствующим элементом мьютекса m.
Effects: Инициализируется pm с помощью tie(m...). Тогда если sizeof...(MutexTypes) есть 0, то никаких эффектов. В противном случае, если sizeof...(MutexTypes) есть 1, то m.lock(). В противном случае lock(m...).
explicit scoped_lock(MutexTypes&... m, adopt_lock_t);
~scoped_lock();
namespace std { template <class Mutex> class unique_lock { public: using mutex_type = Mutex; // [thread.lock.unique.cons], construct/copy/destroy unique_lock() noexcept; explicit unique_lock(mutex_type& m); unique_lock(mutex_type& m, defer_lock_t) noexcept; unique_lock(mutex_type& m, try_to_lock_t); unique_lock(mutex_type& m, adopt_lock_t); template <class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time); template <class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time); ~unique_lock(); unique_lock(const unique_lock&) = delete; unique_lock& operator=(const unique_lock&) = delete; unique_lock(unique_lock&& u) noexcept; unique_lock& operator=(unique_lock&& u); // [thread.lock.unique.locking], locking void lock(); bool try_lock(); template <class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time); template <class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time); void unlock(); // [thread.lock.unique.mod], modifiers void swap(unique_lock& u) noexcept; mutex_type* release() noexcept; // [thread.lock.unique.obs], observers bool owns_lock() const noexcept; explicit operator bool () const noexcept; mutex_type* mutex() const noexcept; private: mutex_type* pm; // exposition only bool owns; // exposition only }; template<class Mutex> unique_lock(unique_lock<Mutex>) -> unique_lock<Mutex>; template <class Mutex> void swap(unique_lock<Mutex>& x, unique_lock<Mutex>& y) noexcept; }
Объект типа unique_lock контролирует владение блокируемым объектом в области. Право собственности на запираемый объект может быть приобретено во время строительства или после строительства и может быть передано после приобретения другому unique_lock объекту. Объекты типа unique_lock нельзя копировать, но можно перемещать. Поведение программы не определено , если содержащаяся указатель pm не является нулевым , и блокируемый объект , на который указывает pm не существует для всего оставшегося lifetime от unique_lock объекта. Поставляемый Mutex тип должен соответствовать BasicLockable требованиям.
[ Note: unique_lock<Mutex> соответствует BasicLockable требованиям. Если Mutex соответствует Lockable требованиям, unique_lock<Mutex> также соответствует Lockable требованиям; если Mutex соответствует TimedLockable требованиям, unique_lock<Mutex> также соответствует TimedLockable требованиям. ] — end note
unique_lock() noexcept;
explicit unique_lock(mutex_type& m);
unique_lock(mutex_type& m, defer_lock_t) noexcept;
unique_lock(mutex_type& m, try_to_lock_t);
Postconditions: pm == addressof(m) и owns == res, где res - значение, возвращаемое вызовом m.try_lock().
unique_lock(mutex_type& m, adopt_lock_t);
template <class Clock, class Duration>
unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time);
Requires: Если mutex_type это не рекурсивный мьютекс, вызывающий поток не владеет мьютексом. Поставляемый Mutex тип должен соответствовать TimedLockable требованиям.
Postconditions: pm == addressof(m) и owns == res, где res - значение, возвращаемое вызовом m.try_lock_until(abs_time).
template <class Rep, class Period>
unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time);
Requires: Если mutex_type это не рекурсивный мьютекс, вызывающий поток не владеет мьютексом. Поставляемый Mutex тип должен соответствовать TimedLockable требованиям.
Postconditions: pm == addressof(m) и owns == res, где res - значение, возвращаемое вызовом m.try_lock_for(rel_time).
unique_lock(unique_lock&& u) noexcept;
Postconditions: pm == u_p.pm и owns == u_p.owns (где u_p - состояние u непосредственно перед этой конструкцией), u.pm == 0 и u.owns == false.
unique_lock& operator=(unique_lock&& u);
Postconditions: pm == u_p.pm и owns == u_p.owns (где u_p - состояние u непосредственно перед этой конструкцией), u.pm == 0 и u.owns == false.
[ Note: С рекурсивным мьютексом оба *this и u могут владеть одним и тем же мьютексом до назначения. В этом случае *this после присвоения мьютекс будет принадлежать и u не будет. ] — end note
~unique_lock();
void lock();
Throws: Любое выброшенное исключение pm->lock(). system_error когда требуется исключение ([thread.req.exception]).
bool try_lock();
Throws: Любое выброшенное исключение pm->try_lock(). system_error когда требуется исключение ([thread.req.exception]).
template <class Clock, class Duration>
bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);
Requires: Поставляемый Mutex тип должен соответствовать TimedLockable требованиям.
Postconditions: owns == res, где res - значение, возвращаемое вызовом try_lock_until(abs_time).
Throws: Любое выброшенное исключение pm->try_lock_until(). system_error когда требуется исключение ([thread.req.exception]).
template <class Rep, class Period>
bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
Requires: Поставляемый Mutex тип должен соответствовать TimedLockable требованиям.
Throws: Любое выброшенное исключение pm->try_lock_for(). system_error когда требуется исключение ([thread.req.exception]).
void unlock();
Throws: system_error когда требуется исключение ([thread.req.exception]).
void swap(unique_lock& u) noexcept;
mutex_type* release() noexcept;
template <class Mutex>
void swap(unique_lock<Mutex>& x, unique_lock<Mutex>& y) noexcept;
bool owns_lock() const noexcept;
explicit operator bool() const noexcept;
mutex_type *mutex() const noexcept;
template <class L1, class L2, class... L3> int try_lock(L1&, L2&, L3&...);
Requires: Каждый тип параметра шаблона должен соответствовать Lockable требованиям. [ Шаблон класса отвечает этим требованиям при соответствующем инстанцирован. ] Note: unique_lock — end note
Effects: Вызывает try_lock() каждый аргумент по порядку, начиная с первого, до тех пор, пока все аргументы не будут обработаны или вызов try_lock() не завершится ошибкой, либо путем возврата, false либо путем выброса исключения. Если вызов для try_lock() завершается неудачно, unlock() должны вызываться все предыдущие аргументы, и не должно быть никаких дальнейших вызовов try_lock().
Returns: -1 если все вызовы try_lock() возвращены true, в противном случае отсчитываемое от нуля значение индекса, указывающее аргумент, для которого был try_lock() возвращен false.
template <class L1, class L2, class... L3> void lock(L1&, L2&, L3&...);
Requires: Каждый тип параметра шаблона должен отвечать Lockable требованиям, [ шаблонный класс отвечает этим требованиям при соответствующем инстанцирован. ] Note: unique_lock — end note
Effects: Все аргументы заблокированы через последовательность звонков lock(), try_lock()или unlock() по каждому аргументу. Последовательность вызовов не должна приводить к тупиковой ситуации, но иначе не определена. [ Note: Должен использоваться алгоритм предотвращения взаимоблокировок, такой как попытка и откат, но конкретный алгоритм не указан, чтобы избежать чрезмерных ограничений реализаций. ] Если вызов или вызывает исключение, должен вызываться для любого аргумента, который был заблокирован вызовом или . — end note lock() try_lock() unlock() lock() try_lock()
namespace std { struct once_flag { constexpr once_flag() noexcept; once_flag(const once_flag&) = delete; once_flag& operator=(const once_flag&) = delete; }; }
Класс once_flag представляет собой непрозрачную структуру данных, которая call_once используется для инициализации данных, не вызывая гонки данных или взаимоблокировки.
constexpr once_flag() noexcept;
template<class Callable, class... Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args);
Requires:
INVOKE(std::forward<Callable>(func), std::forward<Args>(args)...)
(см. [func.require]) должно быть допустимым выражением.
Effects: Выполнение того, call_once что не вызывает его, func есть passive казнь. Выполнение call_once этого вызова func - это active исполнение. Активное исполнение вызовет INVOKE(std::forward<Callable>(func), std::forward<Args>(args)...). Если такой вызов func вызывает исключение, выполняется exceptional, в противном случае - это так returning. Исключительное выполнение должно передать исключение вызывающему call_once. Среди всех выполнений call_once для любого данного once_flag: не более одного должно быть повторным выполнением; если есть возвращающееся исполнение, это будет последнее активное выполнение; и есть пассивные казни, только если есть возвращающееся исполнение. [ Note: Пассивное выполнение позволяет другим потокам надежно наблюдать за результатами, полученными ранее выполненным возвратом. ] — end note
Synchronization: Для любого данного once_flag: все активные исполнения происходят в общем порядке; завершение активного выполнения synchronizes with начало следующего в этом общем порядке; и возвращающееся выполнение синхронизируется с возвратом из всех пассивных исполнений.
Throws: system_error когда требуется исключение ([thread.req.exception]) или любое исключение, созданное func.
[ Example:
// global flag, regular function void init(); std::once_flag flag; void f() { std::call_once(flag, init); } // function static flag, function object struct initializer { void operator()(); }; void g() { static std::once_flag flag2; std::call_once(flag2, initializer()); } // object flag, member function class information { std::once_flag verified; void verifier(); public: void verify() { std::call_once(verified, &information::verifier, *this); } };
— end example ]