Оператор присваивания '=' атомный?

Я использую межканальную связь с использованием глобальной переменной.

//global var
volatile bool is_true = true;

//thread 1
void thread_1()
{
    while(1){
        int rint = rand() % 10;
        if(is_true) {
            cout << "thread_1: "<< rint <<endl;  //thread_1 prints some stuff
            if(rint == 3)
                is_true = false;  //here, tells thread_2 to start printing stuff
        }
    }
}

//thread 2
void thread_2()
{
    while(1){
        int rint = rand() % 10;
        if(! is_true) {  //if is_true == false
            cout << "thread_1: "<< rint <<endl;  //thread_2 prints some stuff
            if(rint == 7)  //7
                is_true = true;  //here, tells thread_1 to start printing stuff
        }
    }
}

int main()
{
    HANDLE t1 = CreateThread(0,0, thread_1, 0,0,0);
    HANDLE t2 = CreateThread(0,0, thread_2, 0,0,0);
    Sleep(9999999);
    return 0;
}

Вопрос

В приведенном выше коде я использую глобальный var volatile bool is_true для переключения печати между thread_1 и thread_2.

Мне интересно , можно ли использовать поточную операцию с помощью потоков,

Ответ 1

Этот код не гарантируется в потоковом режиме на Win32, поскольку Win32 гарантирует атомарность только для правильно выровненных 4-байтовых и указательных значений. bool не может быть одним из этих типов. (Обычно это 1-байтовый тип.)

Для тех, кто требует фактического примера того, как это может произойти:

Предположим, что bool является 1-байтовым типом. Предположим также, что ваша переменная is_true хранится рядом с другой переменной bool (пусть ее называют other_bool), так что обе они имеют одну и ту же 4-байтную строку. Для конкретности скажем, что is_true находится по адресу 0x1000, а other_bool - по адресу 0x1001. Предположим, что оба значения изначально false, и один поток решает обновить is_true, в то время как другой поток пытается обновить other_bool. Возможна следующая последовательность операций:

  • В потоке 1 готовится установка is_true в true путем загрузки 4-байтового значения, содержащего is_true и other_bool. Тема 1 читает 0x00000000.
  • В потоке 2 готовится установка other_bool в true путем загрузки 4-байтового значения, содержащего is_true и other_bool. Тема 2 читает 0x00000000.
  • Тема 1 обновляет байт в 4-байтовом значении, соответствующем is_true, производя 0x00000001.
  • В потоке 2 обновляется байт в 4-байтовом значении, соответствующем other_bool, производя 0x00000100.
  • В потоке 1 сохраняется обновленное значение в памяти. is_true теперь true и other_bool теперь false.
  • В потоке 2 сохраняется обновленное значение в памяти. is_true теперь false и other_bool теперь true.

Обратите внимание, что в конце этой последовательности обновление до is_true было потеряно, потому что оно было перезаписано потоком 2, который зафиксировал старое значение is_true.

Так получилось, что x86 очень прощает этот тип ошибок, потому что он поддерживает байт-гранулированные обновления и имеет очень жесткую модель памяти. Другие процессоры Win32 не так просты. Например, чипы RISC часто не поддерживают байт-гранулярные обновления, и даже если они это делают, они обычно имеют очень слабые модели памяти.

Ответ 2

нет, его нет..... вам нужно использовать какой-то фиксирующий примитив. В зависимости от платформы вы можете использовать boost, или, если собираетесь на родные окна, что-то вроде InterlockedCompareExchange.

Фактически, в вашей ситуации вы можете использовать некоторые из механизмов безопасного потока событий, чтобы вы могли "сигнализировать" свой другой поток, чтобы начать делать то, что вы хотите.

Ответ 3

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

Считывает и записывает типы, которые не выровнены естественным образом, например, записывая DWORDs, которые пересекают четыре байтовые границы, не гарантированно являются атомарными. ЦП, возможно, придется делать эти чтения и записи как операции с несколькими шинами, что может позволить другому потоку изменять или видеть данные в середине чтения или записи.

Ответ 4

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

  • Функция rand() может быть не потокобезопасной (не проблема в приведенном здесь коде)
  • вам не следует использовать функцию Win32 CreateThread(), особенно когда вы используете функции libraly CRT, которые (потенциально) используют глобальные переменные. Вместо этого используйте _beginthreadex().