Spec-Zone.ru › Qt 5.11

Переносимость и потокобезопасность

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

  • Функция, являющаяся потокобезопасной, может вызываться одновременно из нескольких потоков, даже когда вызовы используют общие данные, поскольку все ссылки на общие данные сериализуются.
  • Функция, являющаяся переносимой, также может вызываться одновременно из нескольких потоков, но только если каждый вызов использует свои собственные данные.

Следовательно, функция, являющаяся потокобезопасной, всегда переносимой, но функция, являющаяся переносимой, не всегда потокобезопасной.

Расширяя концепцию, класс считается переносимым, если его методы-члены могут безопасно вызываться из нескольких потоков, при условии, что каждый поток использует разный экземпляр класса. Класс считается потокобезопасным, если его методы-члены могут безопасно вызываться из нескольких потоков, даже если все потоки используют один и тот же экземпляр класса.

Примечание: Классы Qt документируются как потокобезопасные только в том случае, если они предназначены для использования несколькими потоками. Если функция не помечена как потокобезопасная или переносимая, она не должна использоваться из разных потоков. Если класс не помечен как потокобезопасный или переносимый, то конкретный экземпляр этого класса не должен использоваться из разных потоков.

Переносимость

Классы C++ часто являются переносимыми, просто потому, что они обращаются только к своим собственным данным-членам. Любой поток может вызвать метод-член экземпляра переносимого класса, при условии, что ни один другой поток не может вызвать метод-член на том же экземпляре класса в одно и то же время. Например, класс Counter ниже является переносимым:

class Counter
{
public:
    Counter() { n = 0; }

    void increment() { ++n; }
    void decrement() { --n; }
    int value() const { return n; }

private:
    int n;
};

Класс не является потокобезопасным, потому что если несколько потоков попытаются изменить член данных n, результат неопределён. Это происходит потому, что операторы ++ и -- не всегда являются атомарными. Действительно, они обычно расширяются до трёх машинных инструкций:

  1. Загрузка значения переменной в регистр.
  2. Инкрементирование или декрементирование значения регистра.
  3. Возврат значения регистра в основную память.

Если поток A и поток B одновременно загружают старое значение переменной, инкрементируют своё значение в регистре и сохраняют его обратно, они перезаписывают друг друга, и переменная инкрементируется только один раз!

Потокобезопасность

Очевидно, что доступ должен быть сериализован: поток A должен выполнить шаги 1, 2, 3 без прерывания (атомарно), прежде чем поток B сможет выполнить те же шаги; или наоборот. Простой способ сделать класс потокобезопасным — защитить все обращения к членам данных с помощью QMutex:

class Counter
{
public:
    Counter() { n = 0; }

    void increment() { QMutexLocker locker(&mutex); ++n; }
    void decrement() { QMutexLocker locker(&mutex); --n; }
    int value() const { QMutexLocker locker(&mutex); return n; }

private:
    mutable QMutex mutex;
    int n;
};

Класс QMutexLocker автоматически блокирует мьютекс в своём конструкторе и разблокирует его при вызове деструктора в конце функции. Блокировка мьютекса гарантирует, что доступ из разных потоков будет сериализован. Член данных mutex объявлен с квалификатором mutable, потому что нам нужно заблокировать и разблокировать мьютекс в value(), что является константной функцией.

Заметки о классах Qt

Многие классы Qt являются переносимыми, но они не сделаны потокобезопасными, поскольку добавление потокобезопасности потребует дополнительных накладных расходов на многократное блокирование и разблокирование QMutex. Например, QString переносимый, но не потокобезопасный. Вы можете безопасно обращаться к разным экземплярам QString из нескольких потоков одновременно, но вы не можете безопасно обращаться к одному и тому же экземпляру QString из нескольких потоков одновременно (если вы не защищаете сами доступ с помощью QMutex).

Некоторые классы и функции Qt являются потокобезопасными. Это в основном классы, связанные с потоками (например, QMutex), и базовые функции (например, QCoreApplication::postEvent()).

Примечание: Терминология в области многопоточности не полностью стандартизирована. POSIX использует определения переносимого и потокобезопасного, которые несколько отличаются для своих C-API. При использовании других библиотек классов C++ с Qt убедитесь, что определения понятны.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.11/threads-reentrancy.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API