Нужен ли 'volatile' в этом многопоточном коде C ++?

Я написал программу для Windows на C ++, которая иногда использует два потока: один фоновый поток для выполнения трудоемкой работы; и еще один поток для управления графическим интерфейсом. Таким образом, программа все еще реагирует на пользователя, что необходимо для возможности прервать определенную операцию. Потоки обмениваются данными через общую переменную bool , для которой установлено значение true , когда поток GUI сигнализирует прерыванию рабочего потока. Вот код, который реализует это поведение (я мы удалили нерелевантные части):

КОД, ВЫПОЛНЕННЫЙ ПОТОКОМ GUI


class ProgressBarDialog : protected Dialog {

    /**
     * This points to the variable which the worker thread reads to check if it
     * should abort or not.
     */
    bool volatile* threadParameterAbort_;

    ...

    BOOL CALLBACK ProgressBarDialog::DialogProc( HWND dialog, UINT message, 
        WPARAM wParam, LPARAM lParam ) {

        switch( message ) {
            case WM_COMMAND :
                switch ( LOWORD( wParam ) ) {

                    ...

                    case IDCANCEL :
                    case IDC_BUTTON_CANCEL :
                        switch ( progressMode_ ) {
                            if ( confirmAbort() ) {
                                // This causes the worker thread to be aborted
                                *threadParameterAbort_ = true;
                            }
                            break;
                        }

                        return TRUE;
                }
        }

        return FALSE;
    }

    ...

};

КОД, ВЫПОЛНЯЕМЫЙ РЕЗЬБОЙ ПРОЦЕССА


class CsvFileHandler {

    /**
     * This points to the variable which is set by the GUI thread when this
     * thread should abort its execution.
     */
    bool volatile* threadParamAbort_;

    ...

    ParseResult parseFile( ItemList* list ) {
        ParseResult result;

        ...

        while ( readLine( &line ) ) {
            if ( ( threadParamAbort_ != NULL ) && *threadParamAbort_ ) {
                break;
            }

            ...
        }

        return result;
    }

    ...

};

threadParameterAbort_ в обоих потоках указывает на переменную bool , объявленную в структуре который передается в рабочий поток при создании. Он объявлен как

bool volatile abortExecution_;

. Мой вопрос: мне нужно здесь использовать volatile , и является ли приведенный выше код достаточным, чтобы гарантировать, что программа поточно-ориентирована? Я объяснил, как обосновать использование volatile здесь (см. этот вопрос для фона), что он будет:

  • предотвращать чтение * threadParameterAbort_ , чтобы использовать кеш и вместо этого получать значение из памяти, и

  • не позволяют компилятору удалить предложение if в рабочем потоке из-за оптимизации.

(Следующий абзац касается только безопасности потоков программы как таковой, и не , повторяю, не не предполагает заявления о том, что энергозависимо ] любым способом обеспечивает любые средства обеспечения безопасности потока.) Насколько я могу судить, он должен быть потокобезопасным, поскольку установка переменной bool должна в большинстве, если не во всех, архитектурах быть атомная операция. Но я могу ошибаться. И меня также беспокоит, может ли компилятор переупорядочить такие инструкции, как нарушение безопасности потоков. Но лучше быть в безопасности (не каламбур), чем потом сожалеть.

РЕДАКТИРОВАТЬ: не включает в себя утверждение о том, что volatile каким-либо образом обеспечивает любые средства обеспечения безопасности потока.) Насколько я могу судить, он должен быть потокобезопасным, как установка Переменная] bool в большинстве, если не во всех, архитектурах должна быть атомарной операцией. Но я могу ошибаться. И меня также беспокоит, может ли компилятор переупорядочить такие инструкции, как нарушение безопасности потоков. Но лучше быть в безопасности (не каламбур), чем потом сожалеть.

РЕДАКТИРОВАТЬ: не включает в себя утверждение о том, что volatile каким-либо образом обеспечивает любые средства обеспечения безопасности потока.) Насколько я могу судить, он должен быть потокобезопасным, как установка Переменная] bool в большинстве, если не во всех, архитектурах должна быть атомарной операцией. Но я могу ошибаться. И меня также беспокоит, может ли компилятор переупорядочить такие инструкции, как нарушение безопасности потоков. Но лучше быть в безопасности (не каламбур), чем потом сожалеть.

РЕДАКТИРОВАТЬ: Небольшая ошибка в моей формулировке заставила вопрос выглядеть так, как будто я спрашивал, достаточно ли volatile для обеспечения безопасности потока. Это не было моей целью - volatile действительно никак не обеспечивает безопасность потоков - но я хотел спросить, если приведенный выше код демонстрирует правильное поведение, гарантирующее, что программа является потоковой сейф.

11
задан Community 23 May 2017 в 12:13
поделиться

6 ответов

Вы не должны полагаться на volatile, чтобы гарантировать потокобезопасность, потому что, хотя компилятор гарантирует, что переменная всегда будет считываться из памяти (а не из кеша регистров), в многопроцессорных средах барьер памяти также будет быть обязательным.

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

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

14
ответ дан 3 December 2019 в 02:51
поделиться

Википедия говорит об этом довольно хорошо.

В C и, следовательно, в C++ ключевое слово volatile было предназначено для обеспечения доступа к отображаемым в память устройствам разрешить использование переменных между setjmp разрешить использование переменных sig_atomic_t в обработчиках сигналов

Операции над volatile переменными не атомарный и не установить надлежащий происходит до отношений для резьба. Это согласно соответствующие стандарты (C, C++, POSIX, WIN32), и это по сути для подавляющего большинства современных реализации. Нестабильный ключевое слово в основном бесполезно в качестве переносная резьбовая конструкция.

9
ответ дан 3 December 2019 в 02:51
поделиться

volatile не является ни необходимым, ни достаточным для многопоточности в C++. Он отключает оптимизации, которые вполне приемлемы, но не обеспечивает таких необходимых вещей, как атомарность.

Изменить: вместо использования критической секции я бы, вероятно, использовал InterlockedIncrement, что дает атомарную запись с меньшими накладными расходами.

Что я обычно делаю, так это подключаю потокобезопасную очередь (или deque) в качестве входных данных для потока.Когда у вас есть что-то для потока, вы просто помещаете в очередь пакет данных, описывающих задание, и поток делает это, когда может. Когда вы хотите, чтобы поток завершил работу в обычном режиме, вы помещаете в очередь пакет «завершения». Если вам нужно немедленное прерывание, вы используете двухстороннюю очередь и помещаете команду «отмена» в начало двухсторонней очереди. Теоретически у этого есть недостаток, заключающийся в том, что поток не прерывается до тех пор, пока он не завершит свою текущую задачу. Все это означает, что вы хотите, чтобы каждая задача оставалась примерно в том же диапазоне размера/задержки, что и частота, с которой вы в настоящее время проверяете флаг.

Этот общий дизайн позволяет избежать целого ряда проблем IPC.

3
ответ дан 3 December 2019 в 02:51
поделиться

По поводу моего ответа на вчерашний вопрос, нет, volatile не нужен. На самом деле многопоточность здесь не при чем.

    while ( readLine( &line ) ) { // threadParamAbort_ is not local:
        if ( ( threadParamAbort_ != NULL ) && *threadParamAbort_ ) {
  1. предотвратить чтение *threadParameterAbort_ для использования кеша и вместо этого получить значение из память и
  2. запретить компилятору удалять предложение if в рабочем потоке. поток из-за оптимизации.

Функция readLine является кодом внешней библиотеки, иначе она вызывает код внешней библиотеки. Следовательно, компилятор не может предположить, что существуют нелокальные переменные, которые он не модифицирует.Как только указатель на объект (или его суперобъект) сформирован, он может быть передан и сохранен где угодно. Компилятор не может отследить, какие указатели попадают в глобальные переменные, а какие нет.

Таким образом, компилятор предполагает, что readLine имеет собственный static bool * to threadParamAbort_, и изменяет значение. Поэтому приходится перезагружать из памяти.

3
ответ дан 3 December 2019 в 02:51
поделиться

Кажется, тот же вариант использования описан здесь: volatile — лучший друг многопоточного программиста от Alexandrescu. В нем говорится, что именно в этом случае (для создания флага) прекрасно можно использовать volatile.

Итак, да именно в этом случае код должен быть правильным. volative предотвратит как чтение из кеша, так и компилятор от оптимизации оператора if.

1
ответ дан 3 December 2019 в 02:51
поделиться

Хотя другие ответы верны, я также предлагаю вам взглянуть на раздел «Специфично для Microsoft» документации MSDN для volatile

0
ответ дан 3 December 2019 в 02:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: