Подтвердить что ты не робот

Можно ли читать общий логический флаг без его блокировки, когда другой поток может установить его (не более одного раза)?

Я бы хотел, чтобы мой поток закрывался более изящно, поэтому я пытаюсь реализовать простой механизм сигнализации. Я не думаю, что я хочу полностью управляемый событиями поток, поэтому у меня есть рабочий с методом, чтобы полностью остановить его, используя критический раздел Monitor (эквивалентно С# lock, я считаю):

DrawingThread.h

class DrawingThread {
    bool stopRequested;
    Runtime::Monitor CSMonitor;
    CPInfo *pPInfo;
    //More..
}

DrawingThread.cpp

void DrawingThread::Run() {
    if (!stopRequested)
        //Time consuming call#1
    if (!stopRequested) {
        CSMonitor.Enter();
        pPInfo = new CPInfo(/**/);
        //Not time consuming but pPInfo must either be null or constructed. 
        CSMonitor.Exit();
    }
    if (!stopRequested) {
        pPInfo->foobar(/**/);//Time consuming and can be signalled
    }
    if (!stopRequested) {
        //One more optional but time consuming call.
    }
}


void DrawingThread::RequestStop() {
    CSMonitor.Enter();
    stopRequested = true;
    if (pPInfo) pPInfo->RequestStop();
    CSMonitor.Exit();
}

Я понимаю (по крайней мере, в Windows) Monitor/lock - это наименее дорогостоящий примитив синхронизации потоков, но я стараюсь избегать чрезмерного использования. Должен ли я обертывать каждое чтение этого логического флага? Он инициализируется значением false и устанавливается только один раз в true, когда запрашивается останов (если он запрошен до завершения задачи).

Мои преподаватели советуют защищать даже bool, потому что чтение/запись может быть не атомарным. Я думаю, что этот флаг с одним выстрелом является исключением, которое доказывает правило?

4b9b3361

Ответ 1

Нельзя читать что-то, возможно, измененное в другом потоке без синхронизации. Какой уровень синхронизации необходим, зависит от того, что вы на самом деле читаете. Для примитивных типов вы должны взглянуть на атомные чтения, например. в виде std::atomic<bool>.

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

Ответ 2

Логическое присвоение является атомарным. Это не проблема.

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

Решение - это забор памяти, который действительно неявно добавляется операциями блокировки, но для одной переменной он переполняет. Просто объявите его volatile.

Ответ 3

Ответ, я считаю, "это зависит". Если вы используете С++ 03, потоки не определены в стандарте, и вам нужно будет прочитать, что скажут ваш компилятор и ваша библиотека потоков, хотя такую ​​вещь обычно называют "доброкачественной расой" и обычно ОК.

Если вы используете С++ 11, доброкачественные расы - это поведение undefined. Даже когда поведение undefined не имеет смысла для базового типа данных. Проблема заключается в том, что компиляторы могут предполагать, что программы не имеют поведения undefined, и делают оптимизацию на основе этого (см. Также часть 1 и часть 2 оттуда). Например, ваш компилятор может решить один раз прочитать флаг и кешировать значение, потому что это поведение undefined для записи в переменную в другом потоке без какого-либо мьютекса или барьера памяти.

Конечно, вполне возможно, что ваш компилятор promises не сделает эту оптимизацию. Вам нужно будет посмотреть.

Самое простое решение - использовать std::atomic<bool> в С++ 11 или что-то вроде Hans Boehm atomic_ops в другом месте.

Ответ 4

Нет, вы должны защищать каждый доступ, поскольку современные компиляторы и cpus переупорядочивают код без ваших задач многопоточности. Доступ к чтению из разных потоков может работать, но не нужно работать.