33 Thread support library [thread]

33.4 Mutual exclusion [thread.mutex]

33.4.3 Mutex requirements [thread.mutex.requirements]

33.4.3.1 In general [thread.mutex.requirements.general]

Объект мьютекса обеспечивает защиту от гонок данных и обеспечивает безопасную синхронизацию данных между ними execution agents. Агент выполнения owns - мьютекс с момента успешного вызова одной из функций блокировки до вызова разблокировки. Мьютексы могут быть рекурсивными или нерекурсивными и могут предоставлять одновременное владение одному или нескольким агентам выполнения. Поставляются как рекурсивные, так и нерекурсивные мьютексы.

33.4.3.2 Mutex types [thread.mutex.requirements.mutex]

mutex types Стандартные библиотеки типов mutex, recursive_­mutex, timed_­mutex, recursive_­timed_­mutex, shared_­mutex, и shared_­timed_­mutex. Они должны соответствовать требованиям, изложенным в этом разделе. В этом описании m обозначает объект типа мьютекса.

Типы мьютексов должны соответствовать Lockable требованиям.

Типы мьютексов должны быть DefaultConstructible и Destructible. Если инициализация объекта типа мьютекса не удалась,system_­error должно быть выброшено исключение типа . Типы мьютексов нельзя копировать или перемещать.

Условия ошибки для кодов ошибок, если таковые имеются, сообщаемые функциями-членами типов мьютексов, должны быть:

  • resource_­unavailable_­try_­again - если какой-либо управляемый тип дескриптора недоступен.

  • operation_­not_­permitted - если поток не имеет права выполнять операцию.

  • invalid_­argument - если какой-либо собственный тип дескриптора, манипулируемый как часть конструкции мьютекса, неверен.

Реализация должна обеспечивать операции блокировки и разблокировки, как описано ниже. В целях определения наличия гонки данных они ведут себя как атомарные операции ([intro.multithread]). Операции блокировки и разблокировки на одном мьютексе должны выполняться в едином общем порядке. [ Note: Это можно рассматривать как modification order мьютекс. ] [ Создание и уничтожение объекта типа мьютекса не обязательно должно быть потокобезопасным; следует использовать другую синхронизацию, чтобы гарантировать, что объекты мьютекса инициализированы и видны другим потокам. ] end noteNote: end note

Выражение m.lock() должно быть правильно сформированным и иметь следующую семантику:

Requires: Если m имеет тип mutex, timed_­mutex, shared_­mutexили shared_­timed_­mutex, вызывающий поток не является владельцем мьютекса.

Effects: Блокирует вызывающий поток до тех пор, пока для вызывающего потока не будет получено право владения мьютексом.

Postconditions: Вызывающий поток владеет мьютексом.

Return type: void.

Synchronization: Предыдущие unlock() операции на одном и том же объекте являются synchronize with этой операцией.

Throws: system_­error когда требуется исключение ([thread.req.exception]).

Error conditions:

  • operation_­not_­permitted - если поток не имеет права выполнять операцию.

  • resource_­deadlock_­would_­occur - если реализация обнаруживает, что возникнет тупик.

Выражение m.try_­lock() должно быть правильно сформированным и иметь следующую семантику:

Requires: Если m имеет тип mutex, timed_­mutex, shared_­mutexили shared_­timed_­mutex, вызывающий поток не является владельцем мьютекса.

Effects: Пытается получить право собственности на мьютекс для вызывающего потока без блокировки. Если право собственности не получено, нет никакого эффекта и try_­lock() немедленно возвращается. Реализация может не получить блокировку, даже если она не удерживается каким-либо другим потоком. [ Note: Этот ложный сбой обычно встречается редко, но допускает интересные реализации, основанные на простом сравнении и обмене (пункт [atomics]). ] Реализация должна гарантировать, что не будет последовательного возврата в отсутствие конкурирующих захватов мьютексов. end note try_­lock() false

Return type: bool.

Returns: true если право собственности на мьютекс было получено для вызывающего потока, в противном случае false.

Synchronization: Если try_­lock() возвращается true, предыдущие unlock() операции с тем же объектом synchronize with это операция. [ Note: Так lock() как не синхронизируется с неудачным последующим try_­lock(), правила видимости достаточно слабы, чтобы о состоянии после отказа было бы мало что известно, даже при отсутствии ложных отказов. ] end note

Throws: Ничего такого.

Выражение m.unlock() должно быть правильно сформированным и иметь следующую семантику:

Requires: Вызывающий поток должен владеть мьютексом.

Effects: Освобождает вызывающий поток от владения мьютексом.

Return type: void.

Synchronization: Эта операция является synchronizes with последующими операциями блокировки, которые получают право собственности на один и тот же объект.

Throws: Ничего такого.

33.4.3.2.1 Class mutex [thread.mutex.class]

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

Поведение программы не определено, если она уничтожает mutex объект, принадлежащий любому потоку, или поток завершается, пока владеет mutex объектом.

33.4.3.2.2 Class recursive_­mutex [thread.mutex.recursive]

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(). Только когда все уровни владения высвобождены, право владения может быть приобретено другим потоком.

Поведение программы не определено, если:

  • он уничтожает recursive_­mutex объект, принадлежащий любому потоку, или

  • поток завершается при владении recursive_­mutex объектом.

33.4.3.3 Timed mutex types [thread.timedmutex.requirements]

timed mutex types Стандартные библиотеки типов timed_­mutex, recursive_­timed_­mutexи shared_­timed_­mutex. Они должны соответствовать требованиям, изложенным ниже. В этом описании m обозначает объект типа мьютекса, rel_­time обозначает объект создания экземпляра durationи abs_­time обозначает объект создания экземпляра time_­point.

Типы синхронизированных мьютексов должны соответствовать TimedLockable требованиям.

Выражение 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

Return type: bool.

Returns: true если право собственности было получено, иначе false.

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

Return type: bool.

Returns: true если право собственности было получено, иначе false.

Synchronization: Если try_­lock_­until() возвращается true, предыдущие unlock() операции с тем же объектом synchronize with это операция.

Throws: Исключения, связанные с тайм-аутом ([thread.req.timing]).

33.4.3.3.1 Class timed_­mutex [thread.timedmutex.class]

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 объектом.

33.4.3.3.2 Class recursive_­timed_­mutex [thread.timedmutex.recursive]

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(). Только когда все уровни владения были освобождены, владение объектом может быть приобретено другим потоком.

Поведение программы не определено, если:

  • он уничтожает recursive_­timed_­mutex объект, принадлежащий любому потоку, или

  • поток завершается при владении recursive_­timed_­mutex объектом.

33.4.3.4 Shared mutex types [thread.sharedmutex.requirements]

Стандартные библиотеки типов shared_­mutex и shared_­timed_­mutex есть shared mutex types. Типы общих мьютексов должны соответствовать requirements of mutex typesи дополнительно должны соответствовать требованиям, изложенным ниже. В этом описании m обозначает объект общего типа мьютекса.

В дополнение к режиму монопольного владения блокировкой, указанному в [thread.mutex.requirements.mutex], общие типы мьютексов предоставляют shared lock режим владения. Несколько агентов выполнения могут одновременно владеть общей блокировкой общего типа мьютекса. Но ни один агент выполнения не должен удерживать разделяемую блокировку, в то время как другой агент выполнения удерживает монопольную блокировку того же общего типа мьютекса, и наоборот. Максимальное количество исполнительных агентов, которые могут совместно использовать общую блокировку для одного общего типа мьютекса, не указано, но должно быть не менее 10000. Если больше, чем максимальное количество исполнительных агентов пытается получить общую блокировку, избыточные исполнительные агенты должны блокировать до тех пор, пока количество разделяемых блокировок не уменьшится ниже максимального значения другими исполнительными агентами, снимающими свою общую блокировку.

Выражение m.lock_­shared() должно быть правильно сформированным и иметь следующую семантику:

Requires: Вызывающий поток не владеет мьютексом.

Effects: Блокирует вызывающий поток до тех пор, пока для вызывающего потока не будет получено совместное владение мьютексом. Если выбрасывается исключение, то для текущего потока не должна быть получена разделяемая блокировка.

Postconditions: Вызывающий поток имеет общую блокировку мьютекса.

Return type: void.

Synchronization: Предыдущие unlock() операции на одном и том же объекте являются synchronize with этой операцией.

Throws: system_­error когда требуется исключение ([thread.req.exception]).

Error conditions:

  • operation_­not_­permitted - если поток не имеет права выполнять операцию.

  • resource_­deadlock_­would_­occur - если реализация обнаруживает, что возникнет тупик.

Выражение m.unlock_­shared() должно быть правильно сформированным и иметь следующую семантику:

Requires: Вызывающий поток должен удерживать общую блокировку мьютекса.

Effects: Снимает общую блокировку мьютекса, удерживаемого вызывающим потоком.

Return type: void.

Synchronization: Эта операция является synchronizes with последующими lock() операциями, которые получают право собственности на один и тот же объект.

Throws: Ничего такого.

Выражение m.try_­lock_­shared() должно быть правильно сформированным и иметь следующую семантику:

Requires: Вызывающий поток не владеет мьютексом.

Effects: Пытается получить совместное владение мьютексом для вызывающего потока без блокировки. Если долевое владение не получено, нет эффекта и try_­lock_­shared() немедленно возвращается. Реализация может не получить блокировку, даже если она не удерживается каким-либо другим потоком.

Return type: bool.

Returns: true если была приобретена блокировка долевого владения, в false противном случае.

Synchronization: Если try_­lock_­shared() возвращается true, предыдущие unlock() операции с тем же объектом synchronize with это операция.

Throws: Ничего такого.

33.4.3.4.1 Class shared_mutex [thread.sharedmutex.class]

namespace std {
  class shared_mutex {
  public:
    shared_mutex();
    ~shared_mutex();

    shared_mutex(const shared_mutex&) = delete;
    shared_mutex& operator=(const shared_mutex&) = delete;

    // Exclusive ownership
    void lock(); // blocking
    bool try_lock();
    void unlock();

    // Shared ownership
    void lock_shared(); // blocking
    bool try_lock_shared();
    void unlock_shared();

    using native_handle_type = implementation-defined; // See [thread.req.native]
    native_handle_type native_handle();                // See [thread.req.native]
  };
}

Класс shared_­mutex предоставляет нерекурсивный мьютекс с семантикой совместного владения.

Класс shared_­mutex должен удовлетворять всем требованиям shared mutex requirements. Это должен быть класс стандартной компоновки (пункт [class]).

Поведение программы не определено, если:

  • он уничтожает shared_­mutex объект, принадлежащий любому потоку,

  • поток пытается рекурсивно получить право собственности на a shared_­mutex, или

  • поток завершается, когда ему принадлежит какое-либо право собственности на a shared_­mutex.

shared_­mutex может быть синонимом shared_­timed_­mutex.

33.4.3.5 Shared timed mutex types [thread.sharedtimedmutex.requirements]

Стандартный тип библиотеки shared_­timed_­mutex - это shared timed mutex type. Общие синхронизированных типы взаимных блокировок должны отвечать требованиям timed mutex types, shared mutex typesи , кроме того , должны отвечать требованиям , изложенным ниже. В этом описании m обозначает объект общего типа синхронизированного мьютекса, rel_­type обозначает объект создания экземпляра durationи abs_­time обозначает объект создания экземпляра time_­point.

Выражение m.try_­lock_­shared_­for(rel_­time) должно быть правильно сформированным и иметь следующую семантику:

Requires: Вызывающий поток не владеет мьютексом.

Effects: Пытается получить право владения разделяемой блокировкой для вызывающего потока в течение относительного времени ожидания ([thread.req.timing]), указанного в rel_­time. Если время, указанное в, rel_­time меньше или равно rel_­time.zero(), функция пытается получить право владения без блокировки (как если бы путем вызова try_­lock_­shared()). Функция должна вернуться в течение тайм-аута, указанного в, rel_­time только если она получила совместное владение объектом мьютекса. [ Note: Как и в случае try_­lock(), нет никакой гарантии, что право собственности будет получено, если блокировка доступна, но ожидается, что реализации приложат для этого серьезные усилия. ] Если выбрасывается исключение, то для текущего потока не должна быть получена разделяемая блокировка. end note

Return type: bool.

Returns: true если была получена разделяемая блокировка, false иначе.

Synchronization: Если try_­lock_­shared_­for() возвращается true, предыдущие unlock() операции с тем же объектом синхронизируются с ([intro.multithread]) этой операцией.

Throws: Исключения, связанные с тайм-аутом ([thread.req.timing]).

Выражение m.try_­lock_­shared_­until(abs_­time) должно быть правильно сформированным и иметь следующую семантику:

Requires: Вызывающий поток не владеет мьютексом.

Effects: Функция пытается получить совместное владение мьютексом. Если abs_­time он уже прошел, функция пытается получить совместное владение без блокировки (как если бы путем вызова try_­lock_­shared()). Функция должна возвращаться до истечения абсолютного тайм-аута ([thread.req.timing]), указанного в, abs_­time только если она получила совместное владение объектом мьютекса. [ Note: Как и в случае try_­lock(), нет никакой гарантии, что право собственности будет получено, если блокировка доступна, но ожидается, что реализации приложат для этого серьезные усилия. ] Если выбрасывается исключение, то для текущего потока не должна быть получена разделяемая блокировка. end note

Return type: bool.

Returns: true если была получена разделяемая блокировка, false иначе.

Synchronization: Если try_­lock_­shared_­until() возвращается true, предыдущие unlock() операции с тем же объектом синхронизируются с ([intro.multithread]) этой операцией.

Throws: Исключения, связанные с тайм-аутом ([thread.req.timing]).

33.4.3.5.1 Class shared_­timed_­mutex [thread.sharedtimedmutex.class]

namespace std {
  class shared_timed_mutex {
  public:
    shared_timed_mutex();
    ~shared_timed_mutex();

    shared_timed_mutex(const shared_timed_mutex&) = delete;
    shared_timed_mutex& operator=(const shared_timed_mutex&) = delete;

    // Exclusive ownership
    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();

    // Shared ownership
    void lock_shared();  // blocking
    bool try_lock_shared();
    template <class Rep, class Period>
      bool
      try_lock_shared_for(const chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
      bool
      try_lock_shared_until(const chrono::time_point<Clock, Duration>& abs_time);
    void unlock_shared();
  };
}

Класс shared_­timed_­mutex предоставляет нерекурсивный мьютекс с семантикой совместного владения.

Класс shared_­timed_­mutex должен удовлетворять всем требованиям shared timed mutex requirements. Это должен быть класс стандартной компоновки (пункт [class]).

Поведение программы не определено, если:

  • он уничтожает shared_­timed_­mutex объект, принадлежащий любому потоку,

  • поток пытается рекурсивно получить право собственности на a shared_­timed_­mutex, или

  • поток завершается, когда ему принадлежит какое-либо право собственности на a shared_­timed_­mutex.