Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
353
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 15 Блокировка в многопоточных приложениях

533

 

 

ли один противоречащий интуиции, хотя и не новый в области компьютинга факт: иногда гораздо выгоднее не выполнять операцию на самом деле, а просто подож дать. Помните, когда мы только начинали, нам говорили никогда не ждать? Но в случае критических секций именно это и следует делать.

Обычно критические секции применяются для защиты небольших данных. Выше я говорил, что критическая секция защищается семафором и переключе ние в режим ядра для ее получения очень накладно. В первоначальном варианте функция EnterCriticalSection просто узнавала, можно ли получить критическую секцию. Если нет, EnterCriticalSection переключалась в режим ядра. Как правило, к тому моменту, когда поток успевает переключиться в режим ядра и обратно, ока зывается, что другой поток уже освободил критическую секцию миллион компь ютерных лет назад. Странный вывод сотрудников Microsoft заключался в том, что при работе на многопроцессорных системах надо проверять, доступна ли кри тическая секция, и, если нет, переходить в состояние спин блокировки и ждать, а потом проверять ее доступность снова. Очевидно, что на однопроцессорных си стемах счетчик циклов спин блокировки игнорируется. Если критическая секция недоступна и после второй проверки, выполняется переключение в режим ядра. Суть сказанного в том, что удержание потока в пользовательском режиме в пас сивном состоянии все же много выгоднее, чем переключение в режим ядра.

Для присвоения значения счетчику циклов спин блокировки критической сек ции служат две функции: InitializeCriticalSectionAndSpinCount, которую следует ис пользовать вместо InitializeCriticalSection, и SetCriticalSectionSpinCount, позво ляющая изменить первоначальное значение вашего счетчика или значение счет чика библиотечного кода, использующего только InitializeCriticalSection. Разу меется, для этого вам понадобится доступ к указателю на критическую секцию из своего кода.

Подобрать значение счетчика спин блокировки может оказаться нелегко. Если у вас есть две три недели для проработки всех сценариев, займите этим начина ющих программистов — они все равно бездельничают. Однако большинству из нас не так везет. Я всегда инициализирую этот счетчик значением 4000. Именно оно используется в Microsoft для куч ОС, и я всегда находил свой код менее тре бовательным, чтобы уменьшать это число. С другой стороны, оно достаточно ве лико, чтобы почти всегда удерживать код в пользовательском режиме.

Не используйте функции CreateThread/ExitThread

Одна из самых коварных ошибок, допускаемых при разработке многопоточных приложений, связана с функцией CreateThread. Конечно, возникает вопрос: если потоки нельзя создавать при помощи CreateThread, как же их вообще создавать? Вместо CreateThread следует всегда использовать _beginthreadex, функцию создания потоков из стандартной библиотеки C. Как вы уже догадались, раз уж CreateThread дополняет функция ExitThread для завершения потока, _beginthreadex тоже имеет соответствующую функцию _exitthreadex, которую также нужно использовать вместо

ExitThread.

Возможно, вы вызываете CreateThread в своей программе и не испытываете проблем. Увы, при этом возможны очень тонкие ошибки, потому что при исполь зовании CreateThread не инициализируется стандартная библиотека C. Работа стан

534 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

дартной библиотеки C основана на некоторых данных, отдельных для каждого потока, и определенные ее функции были разработаны до того, как стали нор мой высокопроизводительные многопоточные приложения. Так, функция strtok хранит обрабатываемую строку в памяти отдельного потока. Функция _beginthreadex гарантирует наличие данных, отдельных для потоков, а также всех остальных вещей, нужных стандартной библиотеке C. Для гарантии правильной очистки потока вызывайте _exitthreadex, которая правильно освобождает ресурсы стандартной библиотеки C, если вам нужно преждевременно завершить поток.

_beginthreadex работает так же и принимает те же параметры, что и CreateThread. Поток завершается возвратом из функции потока или вызовом _endthreadex. Для преждевременного завершения потоков служит _endthreadex. Как и CreateThread, _beginthreadex возвращает описатель потока, который нужно затем передать Close Handle, чтобы избежать утечки описателей.

В документации к _beginthreadex вы увидите функцию стандартной библиоте ки C по имени _beginthread. Избегайте ее, как чумы, потому что, по моему, ее по ведение по умолчанию просто ошибочно. Описатель, возвращаемый _beginthread, кэшируется, поэтому при быстром завершении потока и его перезаписи другим потоком описатель может оказаться неверным. Даже в документации к _beginthread указано, что безопаснее использовать _beginthreadex. При обзоре кода отметьте все вызовы _beginthread и _endthread, чтобы изменить их затем на _beginthreadex и _endthreadex соответственно.

Опасайтесь диспетчера памяти по умолчанию

Одна из компаний хотела сделать серверное приложение максимально быстрым. Когда программисты обнаружили, что увеличение числа потоков, которое, по их мнению, должно было обеспечивать масштабируемость вычислительной мощно сти, не возымело эффекта, они обратились к нам. Одна из первых вещей, кото рые я сделал, заключалась в остановке программы в отладчике и изучении распо ложения каждого потока при помощи окна Threads (потоки).

Приложение интенсивно работало с библиотекой STL, которая, как я говорил при обсуждении WinDBG в главе 8, сама по себе может ухудшать быстродействие, выделяя огромные объемы памяти. Остановив серверное приложение, я хотел увидеть, какие потоки находились в системе управления памятью стандартной библиотеки C. У всех нас есть исходный код управления памятью (ведь вы уста навливаете исходный код стандартной библиотеки C при каждой установке Micro soft Visual Studio, да?), и я увидел, что всю систему управления памятью защищает одна критическая секция. Это всегда пугало меня, так как мне кажется, что это может приводить к проблемам с производительностью. Но когда я взглянул на клиентс кое приложение, то просто пришел в ужас: 38 из 50 потоков были заблокирова ны на критической секции системы управления памятью стандартной библиоте ки C! Большая часть программы находилась в состоянии ожидания, ничего не делая! Стоит ли говорить, что это не вызвало у программистов особой радости.

Для большинства программ поставляемая Microsoft стандартная библиотека C подходит прекрасно, не вызывая проблем с памятью. Однако в более крупных серверных приложениях одна единственная критическая секция может все испор тить. Итак, прежде всего я хочу порекомендовать вам всегда тщательно обдумы

ГЛАВА 15 Блокировка в многопоточных приложениях

535

 

 

вать использование STL и, если избежать этого не удается, обратите внимание на STLPort версии I (см. главу 2). Ранее я уже указывал на многие проблемы с STL. В контексте крупных многопоточных приложений библиотека STL от Microsoft может приводить к появлению узких мест.

Более серьезная проблема: что делать с единственной критической секцией стандартной библиотеки C? Для ее решения нужно предоставить каждому потоку отдельную кучу, а не использовать единственную глобальную кучу для всех пото ков. Это позволило бы потокам никогда не переключаться в режим ядра для вы деления или освобождения памяти. Конечно, создания отдельной кучи для каж дого потока недостаточно, так как порой память выделяется в одном потоке и освобождается в другом. К счастью, эта головоломка имеет три решения.

Первое — коммерческие системы управления памятью, обрабатывающие код работы с кучами отдельных потоков. Жаль, но цены на такие системы просто грабительские, и ваш начальник никогда не согласится на покупку. Второе реше ние обеспечивает значительное повышение производительности Windows 2000

иосновано на усовершенствованиях, внесенных Microsoft в механизм работы куч ОС (куч, создаваемых функцией HeapCreate и используемых при помощи HeapAlloc

иHeapFree). Чтобы задействовать преимущества кучи ОС, можно заменить все выделения памяти при помощи malloc/free соответствующими функциями Heap*. Что до функций new и delete языка C++, то для их замены нужно предоставить глобальные функции. Если ваша программа будет выполняться на многопроцес сорных системах, то третье решение может заключаться в использовании вели колепной библиотеки Hoard, написанной Эмери Бергером (Emery Berger) и пред назначенной для управления памятью многопроцессорных компьютеров (http:// www.hoard.org). Эта библиотека заменяет функции работы с памятью C и C++ и очень быстро работает на многопроцессорных системах. Если из за дублирова ния символов у вас возникнут проблемы с ее компоновкой, укажите компонов щику LINK.EXE ключ командной строки /FORCE:MULTIPLE. Помните, что Hoard пред назначена для многопроцессорных систем, поэтому на однопроцессорных ком пьютерах она может работать даже медленнее, чем диспетчер памяти по умол чанию.

Получайте дампы в реальных условиях

Один из наиболее огорчительных случаев имеет место, когда ваша программа блокируется в реальных условиях и, несмотря на все усилия, вы не можете вос произвести ошибку. Однако, благодаря последним усовершенствованиям библио теки DBGHELP.DLL, вы больше никогда не окажетесь в такой ситуации. Новые фун кции работы с минидампами позволяют сделать снимок блокировки и отладить ее в удобное для вас время. Функцию записи минидампа и мою улучшенную обо лочку для нее, SnapCurrentProcessMiniDump, находящуюся в библиотеке BUGSLAYER UTIL.DLL, я описал в главе 13.

Чтобы получить дамп в реальных условиях, нужно просто создать фоновый по ток, который создает и ожидает некоторое событие. При возникновении собы тия поток должен вызывать SnapCurrentProcessMiniDump и записывать дамп на диск. Соответствующая функция показана в следующем фрагменте псевдокода. Для ус

536 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

тановки события создайте отдельный исполняемый файл и скажите пользовате лям запускать его в нужной ситуации.

DWORD WINAPI DumperThread ( LPVOID )

 

{

 

HANDLE hEvents[2] ;

 

hEvents[0] = CreateEvent ( NULL

,

TRUE

,

FALSE

,

_T (

"DumperThread" ) ) ;

hEvents[1] = CreateEvent ( NULL

,

TRUE

,

FALSE

,

_T (

"KillDumperThread" ) ) ;

int iRet = WaitForMultipleObjects ( 2 , hEvents , FALSE , INFINITE); while ( iRet != 1 )

{

// Возможно, каждому файлу следует присваивать уникальное имя. SnapCurrentProcessMiniDump ( MiniDumpWithFullMemory ,

_T ( "Program.DMP" ) ) ;

iRet = WaitForMultipleObjects ( 2 , hEvents , FALSE , INFINITE);

}

VERIFY ( CloseHandle ( hEvents[ 0 ] ) ) ; VERIFY ( CloseHandle ( hEvents[ 1 ] ) ) ; return ( TRUE ) ;

}

Уделяйте особое внимание обзору кода

Если вам на самом деле нужно включить в свое приложение многопоточные фраг менты, им нужно уделять повышенное внимание во время обзоров кода. При этом я советую назначать по одному человеку на каждый поток и каждый объект син хронизации. Обзор кода многопоточных приложений «многопоточен» во многих отношениях.

При обзоре кода представьте, что каждый поток выполняется с приоритетом реального времени на собственном процессоре, никогда не прерываясь. Просмат ривая код, каждый «наблюдатель за потоком» уделяет внимание только тем учас ткам, которые выполняются его потоком. Когда «наблюдатель за потоком» полу чает объект синхронизации, к нему подходит «наблюдатель за этим объектом». При освобождении объекта синхронизации «наблюдатель за объектом» уходит в нейтральный угол комнаты. Помимо представителей потоков и объектов, надо назначить нескольких программистов, наблюдающих за общей активностью по токов. Они должны оценивать общий ход выполнения программы и помогать искать места блокировки потоков.

Выполняя обзор кода, помните, что ваш процесс работает и с объектами син хронизации ОС, которые также могут привести к блокировке. В качестве приме ров таких объектов можно привести критическую секцию процесса, описывае

ГЛАВА 15 Блокировка в многопоточных приложениях

537

 

 

мую в разделе «Отладка: фронтовые очерки. Взаимоблокировка, не имеющая смыс ла», и печально известный мьютекс Win16 в Microsoft Windows 9x/Me. Обращай те внимание на все, что может вызвать конкуренцию в вашей программе.

Тестируйте многопоточные приложения на многопроцессорных компьютерах

Как я говорил, многопоточные приложения требуют более серьезного тестиро вания, чем однопоточные. Самый важный совет по поводу тестирования много поточных приложений таков: тщательно тестируйте их на многопроцессорных компьютерах. Я имею в виду не просто выполнение нескольких тестов, а непре рывное тестирование с использованием всех возможных сценариев. Даже если ваша программа прекрасно работает на однопроцессорных машинах, ее запуск на многопроцессорном компьютере может указать на блокировки, о возможности которых вы даже не подозревали.

В идеале тестирование приложения на многопроцессорных компьютерах нужно выполнять каждый день. Если вы руководитель и в вашем отделе нет многопро цессорных систем, немедленно прекратите чтение и снабдите ими половину своих программистов и тестировщиков отдела контроля качества! Если вы — програм мист, не имеющий многопроцессорного компьютера, покажите эту главу своему начальнику и потребуйте у него нужное для работы оборудование! Я получил несколько писем, в которых люди утверждали, что это на самом деле помогало, поэтому не колеблясь идите к начальнику и скажите ему, чтобы компания предо ставила вам такую систему. Если что, ссылайтесь на Джона Роббинса.

Отладка: фронтовые очерки

Как я спас нескольких людей от увольнения

Боевые действия

Когда вице президент одной компании позвонил мне и сказал, что хотел бы нанять меня для решения проблемы с блокировкой, я понял, что работа предстоит нелегкая. Он был весьма раздражен и недоволен тем, что его компании пришлось прибегнуть к услугам консультанта. Он позвонил двум ведущим программистам, и мы вчетвером собрались на онлайновую кон ференцию. Вице президент злился, что разработчики слишком долго без действовали из за этой ошибки. Представляю, как чувствовали себя эти два программиста, когда их грязное белье отправляли по телефону какому то парню, которого они даже не знали. Как сказал вице президент, они рабо тали над переносом приложения «с настоящей ОС» (UNIX) на «эту (выре зано цензурой) детскую ОС под названием Windows», и это «вычеркнуло из его жизни целый год». Конечно, когда я спросил его, зачем им понадоби лось переносить программу на другую платформу, он признал, что «это было нужно, чтобы остаться на плаву». Я невольно улыбнулся на своем конце провода!

см. след. стр.

538 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

Программисты прислали мне по электронной почте код, и мы начали изучать его, в то время как вице президент с гордым видом расхаживал по своему кабинету. Когда я добрался до места блокировки, у меня тут же вы ступил холодный пот, а сердце забилось гораздо чаще. Я понимал, что если я скажу, что для исправления ошибки нужно только удалить буквы S, E, N и D и напечатать вместо них P, O, S и T, вице президент просто сойдет с ума и, вполне возможно, уволит этих двух разработчиков.

Исход

Какое то время я молчал, собираясь с мыслями. Наконец, глубоко вздохнув и сказав: «Ого, это очень, очень серьезно», — я сообщил вице президенту, что на исправление проблемы нам потребуется несколько часов. Было бы лучше, если б мы с программистами остались одни, потому что наверняка у него есть гораздо более важные дела, чем слушать телефонные разгово ры, состоящие большей частью из перечисления всяких шестнадцатерич ных чисел. Нам повезло: он купился на это, и я сказал программистам, что перезвоню им.

Перезвонив, я сообщил им, что они сделали одну очень частую ошибку, которую допускают многие разработчики программ для UNIX: дело в том, что возврат из функции, посылающей сообщения из одного потока в дру гой, в некоторых версиях UNIX выполняется сразу. Однако в Windows воз врат из SendMessage не происходит, пока сообщение не будет обработано. Я нашел в их коде место, где поток, которому они посылали сообщение, уже был заблокирован на объекте синхронизации, поэтому SendMessage вызывала блокировку. Когда я сказал им, что для исправления проблемы нужно толь ко заменить SendMessage вызовом PostMessage, настроение у них резко упа ло. Тогда я попытался их успокоить, сказав, что непонимание происходя щего было вполне законным. Мы провели остаток дня, разбирая другие вопросы, такие как модификация базовых адресов DLL и создание прило жений с полным набором отладочных символов. Вернувшись к телефон ному разговору с вице президентом, я сказал ему, что это была одна из са мых хитрых ошибок, с которыми мне приходилось сталкиваться, но его про граммисты оказали мне поистине неоценимую помощь. В итоге все оста лись довольны. Вице президент избавился от проблемы, программисты узнали много полезного, а я спас их от увольнения!

Полученный опыт

Если вы работаете над многопоточной программой и хотите передавать сообщения между потоками, тщательно обдумайте взаимодействие объек тов синхронизации и сообщений. В подобной ситуации всегда старайтесь использовать PostMessage. Конечно, если вы передаете при помощи сообщений значения, превышающие по объему 32 бита, вызовы PostMessage не будут работать, потому что переданные вами параметры могут быть повреждены к тому времени, когда другой поток обработает сообщение. В таких случа ях вызывайте SendMessageTimeOut — она по крайней мере выполнит возврат

ГЛАВА 15 Блокировка в многопоточных приложениях

539

 

 

в какой то момент времени, и вы сможете узнать, заблокирован ли другой поток и смог ли он обработать сообщение.

Отладка: фронтовые очерки

Взаимоблокировка, не имеющая смысла

Боевые действия

Разрабатывая приложение, программисты столкнулись с непонятной бло кировкой. Поборовшись с ней пару дней, они обратились ко мне.

Их программа имела интересную архитектуру и была в высокой степе ни многопоточной. Блокировка происходила только в определенных слу чаях, причем всегда в процессе загрузки ряда DLL. Программа блокирова лась при вызове функции WaitForSingleObject, проверявшей, смог ли поток создать некоторые совместно используемые объекты.

Программисты были опытными и уже перепроверили код несколько раз на пример потенциальных взаимоблокировок, однако остались в полном замешательстве. Я еще раз спросил, искали ли они взаимоблокировки, и они заверили меня, что да.

Исход

Я хорошо помню эту ситуацию, потому что она относится к тем случаям, когда я почувствовал себя героем через 5 минут после запуска отладчика. Как только программисты воспроизвели блокировку, я взглянул в окно Call Stack (стек вызовов) и заметил, что программа ожидала описателя потока внутри функции DllMain. При загрузке их программой определенной DLL в ее функции DllMain создавался другой поток. Прежде чем продолжить вы полнение, DllMain вызывала WaitForSingleObject для подтверждения события, гарантирующего, что созданный поток смог правильно инициализировать некоторые важные общие объекты.

Разработчики не знали, что каждый процесс имеет критическую секцию процесса, которую ОС использует для синхронизации различных действий, происходящих за кулисами процесса. Помимо прочего, критическая секция процесса служит для сериализации выполнения DllMain при четырех вари антах ее вызова: DLL_PROCESS_ATTACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH и DLL_PROCESS_DETACH. На причину вызова DllMain указывает ее второй параметр.

Итак, вызов LoadLibrary заставлял ОС захватить критическую секцию процесса, чтобы ОС могла вызвать DllMain по причине DLL_PROCESS_ATTACH. После этого DllMain создавала второй поток. При создании процессом но вого потока ОС всегда захватывает критическую секцию процесса для вы зова функции DllMain каждой загруженной DLL по причине DLL_THREAD_ATTACH. В этой конкретной программе второй поток блокировался потому, что пер вый поток удерживал критическую секцию процесса. К несчастью, первый поток вызывал затем WaitForSingleObject, чтобы убедиться в правильной

см. след. стр.

540 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

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

Полученный опыт

Урок очевиден: чтобы избежать блокировки, связанной с объектами ядра, не вызывайте внутри DllMain функции Wait* или EnterCriticalSection, пото му что критическая секция процесса блокирует остальные потоки. Как вы смогли убедиться, даже опытные программисты ошибаются в многопоточ ных программах, так что еще раз: проблемы подобного типа часто проис ходят там, где вы их ожидаете меньше всего.

Требования к DeadlockDetection

Вероятно, вы заметили, что выше я привел мало рекомендаций по поводу исправ ления блокировок. Большинство советов было профилактическими мерами и касались предотвращения блокировок, а не их разрешения. Всем известно, что исправить блокировки, используя отладчик, непросто. В этом разделе я предос тавлю вам дополнительную помощь — утилиту DeadlockDetection.

Вот основные требования, которыми я руководствовался при ее разработке.

1.Указание точного места блокировки в пользовательском коде. От утилиты, которая только сообщает, что вызов EnterCriticalSection заблокирован, толку мало. Эффективное средство должно указывать адрес (а значит, и исходный файл и номер строки) блокировки, чтобы можно было быстро ее исправить.

2.Отображение объекта синхронизации, вызвавшего блокировку.

3.Вывод информации о заблокированной функции Windows и переданных в нее параметрах. Это позволило бы узнать значения тайм аута и значения, передан ные в функцию.

4.Определение потока, вызвавшего блокировку.

5.Утилита должна быть «легкой», чтобы как можно меньше влиять на пользова тельскую программу.

6.Обработка выводимой информации должна быть расширяемой. Утилита дол жна поддерживать разные способы обработки информации, собранной в си стеме обнаружения блокировок, и давать возможность настройки и расшире ния вывода информации не только вам, но и другим программистам.

7.Средство должно обеспечивать легкую интеграцию с пользовательскими про граммами.

Работая с утилитами, подобными DeadlockDetection, следует помнить, что они неизбежно влияют на поведение исследуемого приложения. Можно рассматри вать это как еще одно наглядное подтверждение принципа неопределенности Гейзенберга. DeadlockDetection сама может вызывать в ваших программах блоки ровки, которые вы иначе не обнаружили бы, потому что выполняемая ею работа

ГЛАВА 15 Блокировка в многопоточных приложениях

541

 

 

по сбору информации тормозит потоки. Я склонен считать это поведение одной из особенностей утилиты, потому что любая возможность блокировки в вашем коде указывает на ошибку, что является первым шагом к ее исправлению. Ошиб ки лучше всегда находить самому, чем оставлять такую радость своим клиентам.

Общие вопросы разработки DeadlockDetection

Чтобы DeadlockDetection удовлетворяла названным требованиям, я должен был ответить на ряд вопросов. Сначала я должен был определить, какие функции нужно отслеживать для воспроизведения полной истории блокировки (табл. 15 1).

Табл. 15-1. Функции, отслеживаемые утилитой DeadlockDetection

Тип

Функции

Функции работы с потоками

CreateThread, ExitThread, SuspendThread, ResumeThread,

 

TerminateThread, _beginthreadex, _beginthread,

 

_exitthreadex, _exitthread, FreeLibraryAndExitThread

Функции работы

InitializeCriticalSection,

с критическими секциями

InitializeCriticalSectionAndSpinCount,

 

DeleteCriticalSection, EnterCriticalSection,

 

LeaveCriticalSection, SetCriticalSectionSpinCount,

 

TryEnterCriticalSection

Функции работы с мьютексами

CreateMutexA, CreateMutexW, OpenMutexA, OpenMutexW,

 

ReleaseMutex

Функции работы с семафорами

CreateSemaphoreA, CreateSemaphoreW, OpenSemaphoreA,

 

OpenSemaphoreW, ReleaseSemaphore

Функции работы с событиями

CreateEventA, CreateEventW, OpenEventA, OpenEventW,

 

PulseEvent, ResetEvent, SetEvent

Функции блокировки

WaitForSingleObject, WaitForSingleObjectEx,

 

WaitForMultipleObjects, WaitForMultipleObjectsEx,

 

MsgWaitForMultipleObjects, MsgWaitForMultipleObjectsEx,

 

SignalObjectAndWait

Специальные функции

CloseHandle, ExitProcess, GetProcAddress, LoadLibraryA,

 

LoadLibraryW, LoadLibraryExA, LoadLibraryExW, FreeLibrary

 

 

Обдумав проблему сбора информации, необходимой для удовлетворения пер вых четырех требований, я понял, что мне нужно перехватывать функции из табл. 15 1 (устанавливать для них ловушки), регистрируя получение и освобождение объектов синхронизации. Перехват — нетривиальная задача; ее решение я рас смотрю в разделе «Перехват импортируемых функций». Для перехвата импорти руемых функций код DeadlockDetection должен находиться в DLL, потому что ловушки работают только в том адресном пространстве, в котором создаются. Это значит, что пользователь должен загружать DLL утилиты DeadlockDetection в свое адресное пространство. Данное требование не такое уж и жесткое, если учесть все его достоинства. Реализованная в форме DLL, утилита допускала бы легую интег рацию с пользовательской программой, что позволило бы удовлетворить требо вание 7.

Вы могли заметить, что я не включил в табл. 15 1 некоторые функции работы с сообщениями, способные вызывать блокировку, такие как SendMessage, PostMessage

542 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

и WaitMessage. Сначала я намеревался реализовать и их поддержку, однако когда я запустил под управлением DeadlockDetection классическую программу Чарльза Петцольда (Charles Petzold) «Hello World!» с графическим пользовательским ин терфейсом, DeadlockDetection сообщила столько вызовов, что работа программы

вконечном счете была нарушена. Чтобы сделать DeadlockDetection как можно компактнее и быстрее, мне пришлось отказаться от этих функций.

Решение проблемы сбора информации, удовлетворяющей требованиям 1–4, прямо вытекает из выбранного мной подхода внутрипроцессного перехвата фун кций. Это значит, что при любом вызове функций работы с потоками и функций синхронизации управление будет передаваться в код DeadlockDetection со всей нужной мне информацией.

Сделать DeadlockDetection максимально компактной и быстрой (требование 5) оказалось довольно трудно. Я старался, чтобы код был как можно более эффек тивным, однако в связи с заданными мной целями при этом возникли трудности. Так как вам лучше известно, какие типы объектов синхронизации вы используете

всвоей программе, я решил сгруппировать их, чтобы вы могли указать именно те функции, которые хотите перехватывать. Скажем, если вас интересует только блокировка на мьютексах, вы можете обрабатывать только функции работы с мьютексами.

Япозволяю во время выполнения указывать, какие наборы функций работы с объектами синхронизации вы хотите отслеживать. Кроме того, вы можете вклю чать/отключать DeadlockDetection любое число раз. Вы даже можете назначить своей программе сочетание клавиш (accelerator) или специальный пункт меню, который включает/выключает всю систему DeadlockDetection. Такое ограничение области и времени действия необходимо для соответствия требованию 5 и по могает удовлетворить требованию 7.

После этого мне осталось разобраться только с требованием 6: обеспечить максимальную расширяемость обработки выводимой информации. Я хотел пре доставить вам широкие возможности конфигурирования параметров вывода, а не навязывать какой либо жестко закодированный формат. Отделив перехват функ ций и основную логику программы от кода вывода, я смог улучшить возможность повторного использования кода, потому что разработать только новый модуль вывода гораздо проще, чем переписывать ядро программы. Я назвал модули вы вода расширениями DeadlockDetection или, сокращенно, DeadDetExt. DeadDetExt — это просто DLL, которые экспортируют несколько функций, вызываемых Dead lockDetection в случае необходимости.

Что ж, пришло время описать работу с DeadlockDetection.

Использование DeadlockDetection

Перед использованием DeadlockDetection нужно разместить в одном месте DEAD LOCKDETECTION.DLL, ее файл инициализации и нужную библиотеку DeadDetExt. Файл инициализации — это простой файл INI, в котором должно быть указано хотя бы имя загружаемого файла DeadDetExt. Например, файл DEADLOCKDETEC TION.INI, который загружает поставляемую вместе с утилитой библиотеку TEXT FILEDDEXT.DLL, содержит следующую информацию:

Соседние файлы в предмете Программирование на C++