Не совсем ответ:
Тестирование многопоточных ошибок очень сложно. Большинство ошибок отображаются только в том случае, если два (или более) потока идут в определенные места кода в определенном порядке.
Если и когда это условие будет выполнено, может зависеть от времени выполнения процесса. Это время может измениться из-за одного из следующих предварительных условий:
- Тип процессора
- Скорость процессора
- Количество процессоров / ядер
- Уровень оптимизации
- Запуск внутри или снаружи отладчика
- Операционная система
Наверняка есть еще предварительные условия, которые я забыл.
Поскольку ошибки MT так сильно зависят от точной синхронизации кода, в котором используется «принцип неопределенности» Гейзенберга: если вы хотите проверить ошибки MT, измените синхронизацию с помощью «мер», которые могут предотвратить ошибку. происходит ...
Временные особенности делают ошибки MT настолько недетерминированными.
Другими словами: у вас может быть программное обеспечение, которое запускается месяцами, а затем выходит из строя однажды, а после этого может работать годами. Если у вас нет отладочных журналов / дампов ядра и т. Д., Вы никогда не узнаете, почему он выходит из строя.
Итак, мой вывод таков: нет действительно хорошего способа модульного тестирования для обеспечения безопасности потоков. При программировании всегда нужно держать глаза открытыми.
Чтобы прояснить это, я приведу (упрощенный) пример из реальной жизни (я столкнулся с этим, когда менял своего работодателя и просматривал там существующий код):
Представьте, что у вас есть класс. Вы хотите, чтобы этот класс автоматически удалялся, если никто больше его не использует. Итак, вы строите счетчик ссылок в этом классе:
(Я знаю, что это плохой стиль - удалять экземпляр класса в одном из его методов. Это из-за упрощения реального кода, который использует класс Ref для обработки подсчитанных ссылок.)
class A {
private:
int refcount;
public:
A() : refcount(0) {
}
void Ref() {
refcount++;
}
void Release() {
refcount--;
if (refcount == 0) {
delete this;
}
}
};
Это выглядит довольно просто и не о чем беспокоиться. Но это не потокобезопасно!
Это потому, что refcount ++ и refcount-- не являются атомарными операциями, но обе являются тремя операциями:
- чтение рефконта из памяти для регистрации
- регистр увеличения / уменьшения
- запись refcount из регистра в память
Каждая из этих операций может быть прервана, и другой поток может одновременно манипулировать этим же счетом. Так, если, например, два потока хотят увеличить refcount, может произойти следующее:
- Поток A: считывание refcount из памяти для регистрации (refcount: 8)
- Тема A: инкрементный регистр
- Поток B: считывание refcount из памяти для регистрации (refcount: 8)
- Тема B: инкрементный регистр
- Поток B: запись повторного счета из регистра в память (refcount: 9)
- Поток A: записать refcount из регистра в память (refcount: 9)
Итак, результат: refcount = 9, но это должно было быть 10!
Эту проблему можно решить только с помощью атомарных операций (например, InterlockedIncrement () и InterlockedDecrement () в Windows).
Эта ошибка просто не поддается проверке! Причина в том, что крайне маловероятно, что два потока одновременно пытаются изменить счетчик одного и того же экземпляра и что между этим кодом есть контекстные переключатели.
Но это может случиться! (Вероятность возрастает, если у вас многопроцессорная или многоядерная система, поскольку для этого не требуется переключение контекста).
Это произойдет через несколько дней, недель или месяцев!