такое оператор присваивания '=' атомарный? - PullRequest
24 голосов
/ 28 ноября 2011

Я реализую Inter-Thread Communication, используя глобальную переменную.

//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;
}

Вопрос

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

Интересно является ли потокобезопасным использование операции присваивания здесь ?

Ответы [ 4 ]

57 голосов
/ 28 ноября 2011

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

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

Предположим, что bool является однобайтовым типом. Предположим также, что ваша переменная 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 часто не поддерживают байт-гранулярные обновления, и даже если они это делают, у них обычно очень слабые модели памяти.

7 голосов
/ 28 ноября 2011

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

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

4 голосов
/ 09 октября 2012

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

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

0 голосов
/ 28 ноября 2011

Потокобезопасность этого фрагмента кода не зависит от атомарности назначения.Обе потоковые процедуры работают строго по очереди.Условие гонки отсутствует: thread_1 будет выводить данные до тех пор, пока не получит определенное случайное число, после которого выйдет из «секции вывода» и позволит другому потоку работать в нем.Однако стоит отметить несколько моментов: функция

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