Доброго дня.
Хочу поделится недавно раскопанными граблями, поиск которых занял довольно много времени.
Итак, было некоторое приложение, состоящее из набора DLL-ек и простенького EXE-шника. EXE-шник грузил по очереди DLL-ки, те создавали нужные объекты и вся система раскручивалась. Работало все долго и стабильно. Более того, часть DLL-к не менее успешно работала и в других приложениях.
Но, в один прекрасный день приложение вдруг ни с того ни с сего стало зависать при старте. Причем не всегда, а эпизодически, в зависимости от машины. Например, на двухядерной зависала в 99.9% случаев, а на одноядерной -- с разной степенью вероятности (вплоть до отсутствия зависаний вообще).
Длительная трасировка с помощью различных средств показала, что приложение попадает в тупиковую ситуацию при определенных стечениях обстоятельств. Проблема была в том, чтобы определить, что это за обстоятельства и кто же кого ждет. Выяснилась интересная штука -- зависание происходит при совпадении моментов вызова _beginthread на контексте одной нити и работы конструкторов глобальных переменных в DLL на контексте другой нити.
В упрощенной схеме события развиваются следующим образом:
* есть нить A, которая выполняет создание еще одной нити B;
* есть нить C, которая выполняет загрузку очередной DLL;
* нить A захватывает мутекс m, вызывает _beginthread и ждет, когда нить B подтвердит свой старт посредством события e;
* в этот момент нить C выполняет LoadLibrary, в которой начинают работать конструкторы глобальных переменных в DLL. Одна из переменных пытается захватить мутекс m;
* нить B не получает управления (не смотря на то, что _beginthread успешно завершил работу).
Так образуется дедлок: A владеет m и ждет e от B, C ждет m, а B не может стартовать, чтобы взвести e.
Вопрос в том, почему же B не получает управления? Ответа на него я не знаю, но предполагаю, что здесь используется какой-то внутренний мутекс Run-Time Library от Visual C++. Этот мутексе захватывается в моменты инициализации глобальных переменных при подгрузке очередной DLL. А так же в моменты старта новой нити (вероятно, для того, чтобы сделать копии глобальных переменных для новой нити). Вот на этом-то мутексе как раз и повисают нити C и B (т.е. для старта B нужно, чтобы C освободила внутренний мутекс, но это невозможно, т.к. C ждет m).
Вот такое вот кино. Мораль -- не следует использовать глобальные переменные в DLL для регистрации чего-нибудь где-нибудь (хотя не всегда от этого можно отказаться).
Здесь минимальный пример, который демонстрирует проявление этого тупика (проявляется под VC++7.1, VC++8.0).
Кстати, забавно, что причина данной проблемы в буквальном смысле приснилась после безрезультатного дня поиска причины тупика