[VC++] Бойтесь глобальных переменных в DLL-ках сидящих
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 28.02.07 11:09
Оценка: 3 (2)
Доброго дня.

Хочу поделится недавно раскопанными граблями, поиск которых занял довольно много времени.

Итак, было некоторое приложение, состоящее из набора 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).

Кстати, забавно, что причина данной проблемы в буквальном смысле приснилась после безрезультатного дня поиска причины тупика


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.