Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторная работа Потоки.doc
Скачиваний:
9
Добавлен:
19.12.2018
Размер:
178.69 Кб
Скачать

3. Функция потока. Создание потока

Каждый поток начинает выполнение с некой входной функции. В первичном потоке таковой является main, wmain, WinMain или wWinMain. При создании вторичного потока, в нем тоже должна быть входная функция, которая выглядит примерно так:

DWORD WINAPI ThreadFunc(PVOID pvParam) { DWORD dwResult = 0;

return(dwResult); }

Функция потока может выполнять любые задачи. Рано или поздно она закончит свою работу и вернет управление. В этот момент поток остановится, память, отведенная под его стек, будет освобождена, а счетчик пользователей его объекта ядра «поток» уменьшится на 1. Когда счетчик обнулится, этот объект ядра будет разрушен. Но, как и объект ядра «процесс», он может жить гораздо дольше, чем сопоставленный с ним поток.

А теперь поговорим о самых важных вещах, касающихся функций потоков:

  • В отличие от входной функции первичного потока, у которой должно быть одно из четырех имен: main, wmain, WinMain или wWinMain, — функцию потока можно назвать как угодно. Однако, если в программе несколько функций потоков, необходимо присвоить им разные имена, иначе компилятор или компоновщик решит, что создается несколько реализаций единственной функции.

  • Поскольку входным функциям первичного потока передаются строковые параметры, они существуют в ANSI- и Unicode-версиях: main - wmain и WinMain - wWinMain. Но функциям потоков передается единственный параметр, смысл которого определяется разработчиком, а не операционной системой. Поэтому здесь нет проблем с ANSI/Unicode.

  • Функция потока должна возвращать значение, которое будет использоваться как код завершения потока. Здесь полная аналогия с библиотекой С/С++: код завершения первичного потока становится кодом завершения процесса.

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

Теперь рассмотрим, как заставить операционную систему создать поток, который выполнит эту функцию.

Для создания потоков в Windows предусмотрена функция:

HANDLE CreateThread(

   PSECURITY_ATTRIBUTES psa, // атрибуты защиты

   DWORD cbStack, // размер стека потока

   PTHREAD_START_ROUTINE pfnStartAddr, // адрес функции потока

   PVOID pvParam, // параметр для передачи в функцию потока

   DWORD fdwCreate, // флаги создания потока

   PDWORD pdwThreadID); // адрес для идентификатора потока

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

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

Рассмотрим параметры данной функции.

Параметр psa является указателем на структуру SECURITY_ATTRIBUTES. Значение NULL – атрибуты защиты по умолчанию. Чтобы дочерние процессы смогли наследовать описатель этого объекта, определите структуру SECURITY_ATTRIBUTES и инициализируйте ее элемент hlnheritHandle значением TRUE.

Параметр cbStack определяет, какую часть адресного пространства поток сможет использовать под свой стек. Значение 0 - CreateThread создает стек для нового потока, используя информацию, встроенную компоновщиком в ЕХЕ-файл. Смотрите ключ компоновщика -

/STACK.[reserve] [,commit]

Аргумент reserve определяет объем адресного пространства, который система должна зарезервировать под стек потока (по умолчанию — 1 Мб). Аргумент commit задает объем физической памяти, который изначально передается области, зарезервированной под стек (по умолчанию — 1 страница).

Параметр pfnStartAddr определяет адрес функции потока, с которой должен будет начать работу создаваемый поток, а параметр pvParam идентичен параметру рvРаrаm функции потока. CreateThread лишь передает этот параметр по эстафете той функции, с которой начинается выполнение создаваемого потока. Таким образом, данный параметр позволяет передавать функции потока какое-либо инициализирующее значение. Оно может быть или просто числовым значением, или указателем на структуру данных с дополнительной информацией. Вполне допустимо и даже полезно создавать несколько потоков, у которых в качестве входной точки используется адрес одной и той же функции. Например, можно реализовать Web-сервер, который обрабатывает каждый клиентский запрос в отдельном потоке. При создании каждому потоку передается свое значение рvParam.

Параметр fdwCreate определяет дополнительные флаги, управляющие созданием потока. Он принимает одно из двух значений. 0 (исполнение потока начинается немедленно) или CREATE_SUSPENDED. В последнем случае система создает поток, инициализирует его и приостанавливает до последующих указаний. Флаг CREATE_SUSPENDED позволяет программе изменить какие-либо свойства потока перед тем, как он начнет выполнять код. Правда, необходимость в этом возникает довольно редко.

Последний параметр pdwThreadID функции CreateThread — это адрес переменной типа DWORD, в которой функция возвращает идентификатор, приписанный системой новому потоку. В Windows 2000 и Windows NT 4 в этом параметре можно передавать NULL (обычно так и делается). В Windows 95/98 это приведет к ошибке, так как функция попытается записать идентификатор потока пo нулевому адресу, что недопустимо. И поток не будет создан.

Примечание:

Если при разработке программных продуктов используется VC ++, то вместо явного вызова функции CreateThread следует производить вызов библиотечной функции _beginthreadex. При явном вызове CreateThread не происходит корректная обработка исключений и возникают утески памяти, которые возникают из-за того, что при завершение не освобождаются блоки памяти, связанные с окружением потока (подробнее см. Дж. Рихтер “Создание эффективных WIN32-приложений с учетом специфики 64-разрядной версии Windows” ).

У функции _beginthreadex тот же список параметров, что и у CreateThread, но их имена и типы несколько отличаются (Группа, которая отвечает в Microsoft за разработку и поддержку библиотеки С/С++, считает, что библиотечные функции не должны зависеть от типов данных Wmdows). Как и CreateThread, функция _beginthreadex возвращает описатель только что созданного потока. Однако из-за некоторого расхождения в типах данных необходимо позаботиться об их приведении к тем, которые нужны функции _beginthreadex. Дж. Рихтер использует небольшой макрос chBEGINTHREADEX, который и делает всю эту работу в исходном коде.

typedef unsigned (__stdcall *PTHREAD_START) (void *);

#define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, \

   pvParam, fdwCreate, pdwThreadID)                 \

      ((HANDLE) _beginthreadex(                     \

         (void *) (psa),                            \

         (unsigned) (cbStack),                      \

         (PTHREAD_START) (pfnStartAddr),            \

         (void *) (pvParam),                        \

         (unsigned) (fdwCreate),                    \

         (unsigned *) (pdwThreadID))) 

Прототип функции _beginthreadex:

unsigned long _beginthreadex(

   void *security, 

   unsigned stack_size, 

   unsigned (*start_address)(void *), 

   void *arglist, 

   unsigned initflag, 

   unsigned *thrdaddr);