Отладка ошибки многопоточности
Столкнулся с некоторой проблемой в своём проекте. Имеются сервисные функции засыпания-пробуждения нитей:
void Thread::resume() {
pthread_mutex_lock(&m_sleepMutex);
m_disableSleep++;
pthread_mutex_unlock(&m_sleepMutex);
pthread_cond_broadcast(&m_sleepCond);
}
static void Thread::sleep() {
Thread* thread = current();
pthread_mutex_lock(&(thread->m_sleepMutex));
while (thread->m_disableSleep <= 0) {
pthread_cond_wait(&(thread->m_sleepCond), &(thread->m_sleepMutex));
}
thread->m_disableSleep--;
pthread_mutex_unlock(&(thread->m_sleepMutex));
}
А также некоторая логика:
Поток 1 взводит некую переменную-флаг, делает работу, а затем делает sleep.
Поток 2 в какой-то момент времени (но точно после установки флага, ибо в нём есть в некотором роде его проверка) сбрасывает флаг, а затем делает resume для потока 1.
Поток 1 просыпается и проверяет, почему его разбудили, проверяя флаг. Если флаг сброшен, то продолжает работу сначала, иначе выходит из цикла, ибо это показатель ошибки, если поток разбудили, а флаг не сбросили.
Проблема в том, что с некоторой небольшой вероятностью происходит состояние ошибки (поток 1 проснулся и увидел, что флаг не сбросили), хотя её быть не должно. При этом единственный код, который вызывает resume таки перед этим безусловно сбрасывает флаг (в настоящий момент других вызовов resume банально нет). Более того, если поставить точку останова на выход из цикла при обнаружении взведённого флага, то отладчик видит, что флаг сброшен.
Я также сделал проверку - передавал в resume булева-параметр (значение по умолчанию сделал false), который сохранял в классе потока. В коде, который сбрасывает флаг, передавал инвертированное текущее значение флага после сброса (то есть true). А в потоке 1 считывал поле класса потока в локальную переменную (а затем присваиваю полю класса false), а затем в новую локальную переменную - текущее состояние флага. В отладчике ставлю точку остановка на команду сразу после создания этих локальных переменных и получаю, что оба флага равны true, хотя такого быть не может (ибо другой поток присваивает полю класса значение обратное флагу).
Флаги описаны как voltatile bool, проект собирается с -O0, но это не помогает. При использовании условных точек остановка баг полностью пропадает (вероятно, многопоточность становится не такой многопоточной, если отладчик постоянно останавливает приложение и проверяет условие). Также можно просто забить на флаг и работать дальше вместо выхода из цикла - всё будет идеально работать. Но это не решение, потому что в будущем не сброшенный флаг будет свидетельствовать об ошибке. Пытался создать минимально работающую демонстрацию бага, но не получилось.
Как вообще такое отлаживать? Такое возможно, чтобы, например, это было следствием работы программы на многоядерной системе - значение флага закешировалось у одного ядра и новое дошло до него не сразу? Я пробовал добавлять __sync_synchronize между сбросом флага и вызовом resume, а также между sleep и проверкой флага, но это не помогло.