Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Многопоточное программирование. Часть 2


Домашняя страница www.devdoc.ru

DevDoc - это новые статьи по программированию каждую неделю.

Заходи и читай!

Домашняя страница Письмо автору Архив рассылки Публикация статьи

Выпуск №31

Здравствуйте уважаемые подписчики, сегодня в номере:

  • Результаты опроса
  • Статья "Многопоточное программирование. Часть 2"

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

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

Результаты опроса

Опросы постоянно проводятся на сайте www.devdoc.ru.

Результаты опроса
Какую среду разработки вы используете?

1Microsoft Visual Studio 6.0
7% ( 10 )
 
2Microsoft Visual Studio 7.0+
25% ( 37 )
 
3Borland Builder
10% ( 15 )
 
4GCC, бесплатные компиляторы
53% ( 78 )
 
5Eclipse
3% ( 4 )
 
6Другое
1% ( 2 )
 

Всего голосов: 146
Последний голос отдан: Понедельник - 26 Ноября 2007 - 15:01:42


Постоянная ссылка на статью (с картинками): http://www.devdoc.ru/index.php/content/view/multi_thread_2.htm

Автор: Кудинов Александр
Последняя модификация: 2007-11-26 18:25:49

Многопоточное программирование. Часть 2.

В предыдущей статье мы рассматривали основы потоков. Теперь перейдем к более интересным вещам.

С каждым потоком связан контекст. Доступ к некоторым его полям возможен через вызовы API функций, а часть полей недоступна для приложения пользователя. Итак, что же содержит контекст:

  1. Состояние регистров процессора, в т.ч. и регистр стека.
  2. Регистр EIP, который содержит адрес следующей команды
  3. Указатель на TLS. Об этом ниже.
  4. Другая статическая информация, о которой говорилось в предыдущей главе.

Когда система производит переключение потоков – она сохраняет состояние процессора и системы в контексте текущего потока. Затем системные переменные и процессор инициализируются новыми значениями из контекста следующего в очереди потока.

Контекст потока записывается в структуру CONTEXT (она определена в заголовочном файле WinNT.h).

typedef struct _CONTEXT { 
// 
// Флаги, управляющие содержимым записи CONTEXT. 
// 
// Если запись контекста используется как входной параметр, тогда раздел, 
// управляемый флагом (когда он установлен), считается содержащим 
// действительные значения, Если запись котекста используется для 
// модификации контекста потока, то изменяются только те разделы, для 
// которых флаг установлен 
// 
// Если запись контекста используется как входной и выходной параметр 
// для захвата контекста потока, возвращаются только те разделы контекста, 
// для которых установлены соответствующие флаги. Запись контекста никогда 
// не используется только как выходной параметр. 
// 
DWORD ContextFlags; 
// 
// Этот раздел определяется/возвращается, когда в ContextFlags установлен 
// флаг CONTEXT_DEBUG_REGISTERS. Заметьте, что CONTEXT_DEBUG_REGISTERS 
// не включаются в CONTEXT_FUlL. 
// 
DWORD Dr0; 
DWORD Dr1; 
DWORD Dr2; 
DWORD Dr3; 
DWORD Dr6; 
DWORD Dr7; 
// 
// Этот раздел определяется/возвращается, когда в ContextFlags 
// установлен флаг CONTEXT_FLOATING_POINT, 
// FLOATING_SAVE_AREA FloatSave; 
// 
// Этот раздел определяется/возвращается, когда в ContextFlags 
// установлен флаг CONTEXT_SEGMENTS 
// 
DWORD SegGs; 
DWORD SegFs; 
DWORD SegEs; 
DWORD SegDs; 
// 
// Этот раздел определяется/возвращается, когда в ContextFlags 
// установлен флаг CONTEXT_INTEGER 
// 
DWORD Edi; 
DWORD Esi, 
DWORD Ebx; 
DWORD Fdx; 
DWORD Ecx; 
DWORD Eax; 
// 
// Этот раздел определяется/возвращается, когда в ContextFlags 
// установлен флаг CONTEXT_CONTROL. 
// 
DWORD Ebp, 
DWORD Eip; 
DWORD SegCs; // следует очистить 
DWORD EFlags, // следует очистить 
DWORD Esp, 
DWORD SegSs; 
// 
// Этот раздел определяется/возвращается, когда в ContextFlags 
// установлен флаг CONTEXT_EXTENDED_REGISTERS 
// Формат и смысл значений зависят от типа процессора. 
// 
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; 
} CONTEXT;

Так из чего же состоит структура CONTEXT Давайте посмотрим. Ее элементы четко соответствуют регистрам процессора. Например, для процессоров x86 в число элементов входят Eax, Ebx, Ecx, Edx и т д.

Windows фактически позволяет заглянуть внутрь объекта ядра "поток" и получить сведения о текущем состоянии регистров процессора. Для этого предназначена функция:

BOOL GetThreadContext( HANDLE hThread, PCONTEXT pContext);

Сначала надо создать экземпляр структуры CONTEXT, инициализировать флаги (см. комментарии в структуре и документацию), а потом использовать ее как аргумент функции. Контекст потока имеет смысл только когда поток на выполняется. Поэтому перед вызовом GetThreadContext надо приостановить выполнение потока с помощью SuspendThread. На самом деле в режиме пользователя доступна только часть контекста. Ее можно считать достоверной, если она получается из кода ядра. Т.к. в этом режиме поток пользователя фактически не выполнит ни одной команды пользовательского режима.

Как видите с помощью контекста можно получать весьма интересную информацию о потоке. Но это еще не все. Windows позволяет менять значения в структуре контекста, а затем вносить изменения в контекст потока! Это действительно дает неограниченный контроль над выполнением потока. Установить новый контекст можно с помощью:

BOOL SetThreadContext( HANDLE hThread, CONST CONTEXT *pContext);

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

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

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

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

Совместимость потоков и стандартной библиотеки

Часть стандартной библиотеки C++ не поддерживает работу с многопоточными приложениями. Классический пример – функция strtok. Эта библиотека разрабатывалась задолго до того, как стали использовать потоки. Поэтому функции обращались к глобальным переменным, что недопустимо без дополнительной синхронизации.

К счастью, последние версии стандартной библиотеки уже умеют работать с потоками. Вам надо только обратить внимание на то, с какой библиотекой вы связываете программу. Так, например, MS VC поставляется с двумя версиями стандартной библиотеки. Одна поддерживает многопоточность, а другая - нет.

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

Чтобы выполнялась инициализация этого блока, необходимо создавать потоки с помощью _beginthreadex, а не CreateThread. Эта функция создает и инициализирует структуру _tiddata
и записывает ее в локальную память потока TLS (Thread Local Storage). Также она оборачивает вызов потоковой функции в SEH фрейм, что позволяет перехватывать необработанные C++ исключения, а также обеспечивает поддержку такой функции, как signal.

Функция _endthreadex производит очистку структуры в TLS и освобождает все ресурсы.

Установка TLS осуществляется функцией TlsSetValue.

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

Поддержка многопоточности сказывается на быстродействии стандартной библиотеки. Поэтому если это критично – надо использовать одно-поточную версию.

Меня часто спрашивают, а что будет, если создавать поток с помощью CreateThread и пользоваться функциями стандартной библиотеки. На самом деле ничего страшного не произойдет и программа в 99% случаев будет работать без глюков. Библиотечные функции, которые требуют tiddata, автоматически выполнят инициализацию этого блока, если это не было сделано ранее. Ошибки могут проявляться при вызове signal, т.к. отсутствует SEH фрейм, а также при завершении потока. При завершении потока будет утечка блока tiddata, т.к. функция endthreadex не будет вызвана.


Если вам нравиться эта рассылка рекомендуйте ее своим друзьям. Подписаться можно по адресу http://subscribe.ru/catalog/comp.soft.prog.devdoc

Copyright (C) Kudinov Alexander, 2006-2007

Перепечатка и использование материалов запрещена без писменного разрешения автора.


В избранное