Интересная "стрессовая" проблема
Первоначально, при работе с примером небольшой консольной программы, MemStress вела себя великолепно. Однако, завершая работу над программой MemStressDemo (которая использует библиотеку классов MFC), я обратил внимание на следующую проблему. Если в режиме диалога с MemStress пользователь (через панель сообщений) требовал, чтобы распределения терпели неудачу, то раздавался ряд звуковых сигналов, и программа MemStressDemo переставала работать. Поскольку ситуация устойчиво воспроизводилась (иначе говоря, я имел возможность дублировать эту проблему), то серьезный стресс возник уже у меня самого, потому что я никак не мог уловить, в чем суть этой проблемы.
После нескольких прогонов, наконец, удалось вновь открыть панель сообщения. Но, вместо того чтобы находиться в центре экрана, она открылась в его нижнем правом углу. Такое поведение панелей сообщений позволяет с достаточной долей уверенности считать, что вызов API-функции MessageBox каким-то образом стал реентерабельным (повторно входимым). Мне показалось, что подключение распределения происходило где-то в середине вызова функции MessageBox. Для проверки гипотезы я установил точку прерывания на первой инструкции функции AiiocationHook, как раз перед вызовом функции MessageBox. Отладчик, как и следовало ожидать, остановился на точке прерывания.
Просмотр стека показал, что прямое обращение к API-функции MessageBox почему-то проходило через код MFC. Трассировка этого кода привела меня внутрь функции _AfxActivationWndProc в строку, которая вызывала метод CWND::FromHandie, выполняющий распределения памяти для того, чтобы MFC могла создавать объекты типа (класса) cobject. Я был немного озадачен — как я там оказался? Но комментарий в коде указал, что функция _AfxActivationWndProc используется для управления активизацией и создает "серые" (неактивные) диалоговые панели. MFC использует СВТ-подключение, чтобы перехватывать создание окна в пространстве процесса. Когда новое окно создается (в моем случае это простая панель сообщения — message box), MFC создает подчиненное окно со своей собственной оконной процедурой.
Тут уровень моего персонального стресса резко поднялся, потому что я не понимал, как следует управлять такой ситуацией. Поскольку повторный вход был в том же самом потоке, невозможно было использовать объект синхронизации типа семафора, т. к. это заблокировало бы поток. Обдумав проблему, я решил включить в код специальный флажок, который индицировал бы повторный вход в функцию AiiocationHook, и устанавливать этот флажок в каждом потоке отдельно, потому что уже имелась критическая секция, защищающая против многопоточного повторного входа в AiiocationHook.
Такая формулировка проблемы позволила понять, что нужна только переменная в локальной памяти потока, обеспечивающая доступ к функции AiiocationHook. Если ее значение больше 0, значит функция AiiocationHook была повторно вызвана в процессе выполнения функции MessageBox, и нужно только освободить эту функцию. Я реализовал специальное решение, и все заработало так, как и было запланировано.
СВТ — Computer-Based Training, машинное обучение. — Пер.
Автор называет это решение "quick dynamic thread local storage solution" быстрым решением для динамической локальной памяти потока. — Пер.