Оператор присваивания '=' атомный?
Я использую межканальную связь с использованием глобальной переменной.
//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().