Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Книга о KOL.doc
Скачиваний:
29
Добавлен:
30.04.2019
Размер:
1.77 Mб
Скачать

5.10. Псевдо-потоки

За двумя зайцами погонишься – ни одного не поймаешь.

(Русская народная пословица)

Самый, пожалуй, большой недостаток многопоточности – это дополнительные барьеры на пути успешной отладки трудноуловимых ошибок. Переключением потоков занимается операционная система, и делает она по своему разумению, не спрашивая нас, когда ей какой поток выполнять, а когда приостанавливать. Эти решения системы зависят от внешних факторов (работа с другими приложениями, сетью, состояние своп-файла на диске, и т.д. и т.п. – разве только погоды на Марсе нет в этом перечне). Ситуация усугубляется в случае многоядерных процессоров, когда несколько потоков в действительности могут выполняться полностью параллельно. Поэтому при нескольких исполнениях одного и того же приложения с одними и теми же данными, даже если вы пытаетесь воспроизвести всю последовательность нажимавшихся кнопок, нет никакой уверенности, что удастся в точности воспроизвести желаемое событие. Особенно это неприятно, если желаемым событием является повторение произошедшей при таких же условиях ошибки в вашей программе.

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

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

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

Для KOL мною придумана замена потоков псевдо-потоками, при которой приложение не изменяет в основном свое поведение, но фактически становится однопоточным. Для превращения потоков в псевдо-потоки достаточно добавить в опции проекта символ условной компиляции PSEUDO_THREADS и выполнить сборку (Build) приложения. Для каждого псевдо-потока, кроме главного потока (представленного глобальной переменной MainThread), выделяется блок памяти для хранения стека. Размер такого блока равен 1 Мбайт по умолчанию, но он может быть изменен присваиванием желаемого значения переменной PseudoThreadStackSize.

Псевдо-потоки, так же как и обычные потоки, могут запускаться (Resume), приостанавливаться (Suspend), и переключаться. Единственное отличие в том, что переключением псевдо-потоков заведует не операционная система, которая теперь считает все приложение однопоточным, а главный псевдо-поток. Переключения происходят автоматически теперь всего в нескольких местах: в методе Applet.ProcessMessage, в процедуре Sleep и в функциях WaitForMultipleObjects и WaitForSingleObject. Разумеется, для расширения функциональности указанных трех API-функций в случае определения символа PSEUDO_THREADS в модуле KOL объявляются свои версии этих функций, способные вызвать метод MainThread.NextThread, когда текущему псевдо-потоку больше нечего делать.

Таким образом, не изменяя код приложения, многопоточное приложение становится однопоточным. Потоки при этом сохраняются, но в несколько усеченном виде. Для целей отладки такая модель может оказаться чрезвычайно полезной, так как псевдо-потоки продолжают «эмулировать» (в основном) поведение потоков. Хотя, без соблюдения некоторых правил использовать такую модель может не получиться. А именно:

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

  • Не следует использовать мультимедийный таймер для организации переключения псевдо-потоков. В случае попытки вызова метода MainThread.SwithToThread или NextThread непосредственно из обработчика события мультимедийного таймера приложение просто сломается, так как вызов будет произведен фактически из реально отдельного потока, создаваемого системой для каждого активного мультимедийного таймера. В случае же выполнения этого действия отправкой сообщения (SendMessage) данное сообщение все равно будет обработано только в обработчике сообщений, т.е. только тогда, когда управление получит главный псевдо-поток, так что особого смысла в таком переключении нет;

  • Не следует использовать то обстоятельство, что при работе с оконными объектами (при их создании) вне главного потока обычно такие окна не появляются на экране, оставаясь невидимыми. Или, если вы создадите обработчики сообщений в дополнительных потоках для того, чтобы в каждом потоке работать со своими оконными объектами, то в случае псевдо-потоков, когда реальный поток только один, данная модель окажется, скорее всего, не рабочей (работать будет только цикл обработки сообщений, запущенный последним).

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

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