#c #multithreading #concurrency #mutex #race-condition
#c #многопоточность #параллелизм #мьютекс #race-условие
Вопрос:
Итак, я читаю книгу в этой главе, в которой рассматриваются многопоточность и параллелизм, они задали мне вопрос, который на самом деле не имеет смысла для меня.
Я предполагаю создать 3 функции с параметром x, которые просто вычисляют x * x; одна использует мьютекс, одна использует атомарные типы, а другая не использует ни одну. И создайте 3 глобальные переменные, содержащие значения. Первые две функции будут предотвращать условия гонки, а третья может и не быть.
После этого я создаю N потоков, а затем выполняю цикл и указываю каждому потоку вычислить его функцию x (3 отдельных цикла, по одному для каждой функции. Итак, я создаю N потоков 3 раза)
Теперь в книге говорится, что, используя функции 1 и 2, я всегда должен получать правильный ответ, но, используя функцию 3, я не всегда получаю правильный ответ. Тем не менее, я всегда получаю правильный ответ на все из них. Я предполагаю, что это потому, что я просто вычисляю x * x, и это все, что он делает.
В качестве примера, когда N = 3, правильное значение 0 * 0 1 * 1 2 * 2 = 5.
это атомарная функция:
void squareAtomic(atomic<int> x)
{
accumAtomic = x * x;
}
И вот как я вызываю функцию
thread threadsAtomic[N]
for (int i = 0; i < N; i ) //i will be the current thread that represents x
{
threadsAtomic[i] = thread(squareAtomic, i);
}
for (int i = 0; i < N; i )
{
threadsAtomic[i].join();
}
Это функция, которая иногда должна создавать условия гонки:
void squareNormal(int x)
{
accumNormal = x * x;
}
Вот как я это называю:
thread threadsNormal[N];
for (int i = 0; i < N; i ) //i will be the current thread that represents x
{
threadsNormal[i] = thread(squareNormal, i);
}
for (int i = 0; i < N; i )
{
threadsNormal[i].join();
}
Это все мой собственный код, поэтому я, возможно, неправильно задаю этот вопрос, и в этом случае я приношу свои извинения.
Комментарии:
1. Какой тип
accumNormal
? Этоint
?2. Да, все они являются типами int
Ответ №1:
Одна из проблем, связанных с условиями гонки (и с неопределенным поведением в целом), заключается в том, что их наличие не гарантирует, что ваша программа будет вести себя неправильно. Скорее, неопределенное поведение только аннулирует гарантию того, что ваша программа будет вести себя в соответствии с правилами спецификации языка C . Это может затруднить обнаружение неопределенного поведения с помощью эмпирического тестирования. (Худшим кошмаром каждого многопоточного программиста является ошибка, которая ни разу не была замечена в течение интенсивного трехмесячного периода тестирования программы и появляется только в виде таинственного сбоя во время большой демонстрации на сцене перед живой аудиторией)
В этом случае условие гонки вашей колоритной программы проявляется в виде одновременного чтения и записи нескольких потоков accumNormal
; в частности, вы можете получить неверный результат, если поток A считывает значение accumNormal
, а затем поток B записывает новое значение accumNormal
, а затем поток A записывает новое значение accumNormal
, перезаписывая потокЗначение B.
Если вы хотите иметь возможность продемонстрировать себе, что условия гонки действительно могут привести к неправильным результатам, вы бы хотели написать программу, в которой несколько потоков работают с одной и той же общей переменной в течение длительного времени. Например, у вас может быть половина потоков, увеличивающих переменную в 1 миллион раз, в то время как другая половина уменьшает переменную в 1 миллион раз, а затем проверяет впоследствии (т. Е. После объединения всех потоков), чтобы увидеть, равно ли конечное значение нулю (чего вы и ожидаете), иесли нет, запустите тест еще раз, и пусть этот тест выполняется всю ночь, если это необходимо. (и даже этого может быть недостаточно для обнаружения неправильного поведения, например, если вы работаете на оборудовании, где приращения и уменьшения реализованы таким образом, что они «просто работают» для этого варианта использования)
Комментарии:
1. Это то, о чем я думал, спасибо за ответ. Итак, я предполагаю, что, хотя я реализовал функцию, которая потенциально может иметь условия гонки, я, возможно, не смогу увидеть ее даже после многократного тестирования.
2. Правильно — вы никогда не сможете доказать правильность своей программы с помощью одного тестирования (потому что независимо от того, сколько раз или способов вы тестируете, этого может быть недостаточно, чтобы вызвать очевидную ошибку), но вы могли бы доказать, что это неверно таким образом (т. Е. Если вы видите ошибку, тогда программа должнабыть неправильным).