Студопедия

КАТЕГОРИИ:

АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция

Приостановка, возобновление и переключение потоков




 В объекте ядра «поток» имеется переменная — счетчик числа простоев данного потока. При вызове CreateProcess или CreateThread он инициализируется значением, равным 1, которое запрещает системе выделять новому потоку процессорное время. Такая схема весьма разумна: сразу после создания поток не готов к выполнению, ему нужно время для инициализации. После того как поток полностью инициализирован, CreateProcess или CreateThread проверяет, не передан ли ей флаг CREATE_SUSPENDED, и, если да, возвращает управление, оставив поток в приостановленном состоянии. В ином случае счетчик простоев обнуляется, и поток включается в число планируемых — если только он не ждет какого-то события (например, ввода с клавиатуры).

Создав поток в приостановленном состоянии, Вы можете настроить некоторые его свойства (например, приоритет, о котором мы поговорим позже). Закончив настройку, вы должны разрешить выполнение потока. Для этого вызовите ResumeThread и передайте описатель потока, возвращенный функцией CreateThread (описатель можно взять и из структуры, на которую указывает параметр ppiProcInfo, передаваемый в CreateProcess).

DWORD ResumeThread(HANDLE hThread);

 

Если вызов ResumeThread прошел успешно, она возвращает предыдущее значение счетчика простоев данного потока; в ином случае — 0xFFFFFFFE Выполнение отдельного потока можно приостанавливать несколько раз. Если поток приостановлен 3 раза, то и возобновлен он должен быть тоже 3 раза — лишь тогда система выделит ему процессорное время. Выполнение потока можно приостановить не только при его создании с флагом CREATE_ SUSPENDED, но и вызовом SuspendThread:

DWORD SuspendThread(HANDLE hThread);

 

Любой поток может вызвать эту функцию и приостановить выполнение другого потока (конечно, если его описатель известен). Хоть об этом нигде и не говорится (но я все равно скажу!), приостановить свое выполнение поток способен сам, а возобновить себя без посторонней помощи — нет. Как и ResumeThread, функция SuspendThread возвращает предыдущее значение счетчика простоев данного потока. Поток можно приостанавливать не более чем MAXIMUM_SUSPEND_COUNT раз (в файле WinNT.h это значение определено как 127).

 Создавая реальное приложение, будьте осторожны с вызовами SuspendThread, так как нельзя заранее сказать, чем будет заниматься его поток в момент приостановки. Например, он пытается выделить память из кучи и поэтому заблокировал к ней доступ. Тогда другим потокам, которым тоже нужна динамическая память, придется ждать его возобновления. SuspendThread безопасна только в том случае, когда вы точно знаете, что делает (или может делать) поток, и предусматриваете все меры для исключения вероятных проблем и взаимной блокировки потоков.

Приостановка и возобновление процессов

 В Windows понятия «приостановка» и «возобновление» неприменимы к процессам, так как они не участвуют в распределении процессорного времени. Однако это можно сделать из другого процесса, причем он должен быть отладчиком и, в частности, вызывать функции вроде WaitForDebugEvent и ContinueDebugEvent. Того же можно добиться с помощью команды Suspend Process утилиты Process Explorer от Sysinternals (см. http://www.microsoft.com/technet/ sysinternals/ProcessesAndThreads/ProcessExplorer.mspx): она приостанавливает все потоки процесса. Других способов приостановки всех потоков процесса в Windows нет: программа, выполняющая такую операцию, может «потерять» новые потоки. Система должна как-то приостанавливать в этот период не только все существующие, но и вновь создаваемые потоки. Майкрософт предпочла встроить эту функциональность в системный механизм отладки.

Вам, конечно, не удастся написать идеальную функцию SuspendProcess, но вполне по силам добиться ее удовлетворительной работы во многих ситуациях. Один из вариантов реализации функции SuspendProcess предлагает Рихтер [8а]. (Файл Рихтер_Назар_Windows via C++.pdf)

 

Функция потока

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

DWORD WINAPI ThreadFunc(PVOID pvParam)

{

DW0RD dwResult = 0;

// некоторый код

return(dwResult);

}

 

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

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

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

2. Входным функциям первичного потока передаются строковые параметры, типы которых которые зависят от используемого набора символов: ANSI или Unicode. Функциям потоков передается один единственный параметр, смысл которого определяется программистом, а не операционной системой. Поэтому здесь нет проблем с ANSI/Unicode.

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

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

 

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

Функция CreateThread

Мы уже говорили, как при вызове функции CreateProcess появляется на свет первичный поток процесса. Если вы хотите создать дополнительные потоки, нужно вызвать из первичного потока функцию CreateThread:

HANDLE CreateThread(

PSECURITY_ATTRIBUTES psa,

DW0RD cbStackSize,

PTHREAD_START_ROUTIME pfnStartAddr,

PVOID pvParam,

DWORD dwCreateFlags,

PDWORD pdwThreadID);

 

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

Замечание. CreateThread — это Windows-функция, создающая поток. Но никогда не вызывайте ее, если вы пишете код на С/С++. Вместо нее вы должны использовать функцию _beginthreadex из библиотеки Visual С++. (Если вы работаете с другим компилятором, он должен поддерживать свой эквивалент функции CreateThread.) Что именно делает _beginthreadex и почему это так важно, будет рассказано позже.

Итак, общее представление о функции CreateThread вы получили.

Рассмотрим, вкратце, назначение параметров этой функции (подробное описание см. в [8а], глава 6).

Параметр psa позволяет задать атрибуты защиты. Если вы хотите, чтобы объекту ядра «поток» были присвоены атрибуты защиты по умолчанию (что чаще всего и бывает), передайте в этом параметре NULL.

Параметр cbStackSize определяет, какую часть адресного пространства поток сможет использовать под свой стек (по умолчанию 1МБ).

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

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

Параметр pdwThreadID – это адрес переменной типа DWORD, в которой функция возвращает идентификатор, приписанный системой новому потоку. Вы можете (чаще всего так и поступают) передавать NULL в этом параметре. Так можно сообщить функции о том, что идентификатор потока вас не интересует.

Завершение потока

Поток можно завершить четырьмя способами:

· функция потока возвращает управление (рекомендуемый способ);

· поток самоуничтожается вызовом функции ExitThread (нежелательный способ);

· один из потоков данного или стороннего процесса вызывает функцию TerminateThread (нежелательный способ);

· завершается процесс, содержащий данный поток (тоже нежелательно).

 

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

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

· любые С++-объекты, созданные данным потоком, уничтожаются соответствующими деструкторами;

· система корректно освобождает память, которую занимал стек потока;

· система устанавливает код завершения данного потока (поддерживаемый объектом ядра «поток») – его и возвращает ваша функция потока;

· счетчик пользователей данного объекта ядра «поток» уменьшается на 1.

 

Функция ExitThread. Поток можно завершить принудительно, вызвав:

void ExitThread(DWORD dwExitCode);

 

При этом освобождаются все ресурсы операционной системы, выделенные данному потоку, но С/С++-ресурсы (например, объекты, созданные из С++- классов) не очищаются. Именно поэтому лучше возвращать управление из функции потока, чем самому вызывать функцию ExitThread. В параметр dwExitCode вы помещаете значение, которое система рассматривает как код завершения потока. Возвращаемого значения у этой функции нет, потому что после ее вызова поток перестает существовать.

Замечание. ExitThread — это Windows-функция, которая уничтожает поток. Но никогда не вызывайте ее, если вы пишете код на С/С++. Вместо нее вы должны использовать функцию _endthreadex из библиотеки Visual С++. (Если вы работаете с другим компилятором, он должен поддерживать свой эквивалент функции ExitThread.) Что именно делает _endthreadex и почему это так важно, я объясню потом.

Функция TermlnateThread. Вызов этой функции также завершает поток:

 BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode);

 

В отличие от ExitThread, которая уничтожает только вызывающий поток, эта функция завершает поток, указанный в параметре hThread.

В параметр dwExitCode вы помещаете значение, которое система рассматривает как код завершения потока. После того как поток будет уничтожен, счетчик пользователей его объекта ядра «поток» уменьшится на 1.

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

Корректно написанное приложение не должно вызывать эту функцию, поскольку поток не получает никакого уведомления о завершении; из-за этого он не может выполнить должную очистку ресурсов.

Если завершается процесс. Функции ExitProcess и TerminateProcess тоже завершают потоки. Единственное отличие в том, что они прекращают выполнение всех потоков, принадлежавших завершенному процессу. При этом гарантируется высвобождение любых выделенных процессу ресурсов, в том числе стеков потоков. Однако эти две функции уничтожают потоки принудительно — так, будто для каждого из них вызывается функция TerminateThread. А это означает, что очистка проводится некорректно: деструкторы С++-объектов не вызываются, данные на диск не сбрасываются и т. д. Как указывалось выше, когда входная функция приложения возвращает управление, стартовый код библиотеки С/С++ вызывает ExitProcess. Поэтому в многопоточном приложении необходимо явно позаботиться о корректной остановке каждого потока до завершения главного потока. В противном случае все потоки «погибнут» внезапно и молча.

Что происходит при завершении потока. А происходит вот что:

· Освобождаются все описатели User-объектов, принадлежавших потоку. В Windows большинство объектов принадлежит процессу, содержащему поток, из которого они были созданы. Сам поток владеет только двумя User-объектами: окнами и ловушками (hooks). Когда поток, создавший такие объекты, завершается, система уничтожает их автоматически. Прочие объекты разрушаются, только когда завершается владевший ими процесс.

· Код завершения потока меняется со STILL_ACTIVE на код, переданный в функцию ExitThread или TerminateThread.

· Объект ядра «поток» переводится в свободное состояние.

· Если данный поток является последним активным потоком в процессе, завершается и сам процесс.

· Счетчик пользователей объекта ядра «поток» уменьшается на 1.

 

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

BOOL GetExitCodeThread( HANDLE hThread, PDWORD pdwExitCode);

 

Код завершения возвращается в переменной типа DWORD, на которую указывает pdwExitCode. Если поток не завершен на момент вызова GetExitCodeThread, функция записывает в эту переменную идентификатор STILL_ACTIVE (0x103). При успешном вызове функция возвращает TRUE.

 

 


Часть2.Технология СОМ

Неплохое введение в СОМ, а также пример реализации сервера (вплоть до реализации фабрики класса) и клиентского приложения на С++ содержится в работе Добрынина (файл Добрынин_Технологии_компонентного_программирования.html).
Также в качестве вводного пособия можно рекомендовать работу Бокс Д. Сущность технологии СОМ. Библиотека программиста. — СПб.: Питер, 2001. — 400 с.: ил. (Есть в электронном формате)

 

Врач, строитель и программистка спорили о том, чья профессия древнее. Врач заметил: "В Библии сказано, что Бог сотворил Еву из ребра Адама. Такая операция может быть проведена только хирургом, поэтому я по праву могу утверждать, что моя профессия самая древняя в мире". Тут вмешался строитель и сказал: "Но еще раньше в Книге Бытия сказано, что Бог сотворил из хаоса небо и землю. Это было первое и, несомненно, наиболее выдающееся строительство. Поэтому, дорогой доктор, вы не правы. Моя профессия самая древняя в мире". Программистка при этих словах откинулась в кресле и с улыбкой произнесла: "А кто же, по-вашему, сотворил хаос?"         Гради Буч (Grady Booch)

 










Последнее изменение этой страницы: 2018-04-12; просмотров: 447.

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