Системное программирование в среде Windows
Шрифт:
Перспективы и проблемы
Согласно принятой в этой и последующих главах точке зрения использование потоков не только позволяет упростить проектирование и реализацию некоторых программ, но и (при условии соблюдения нескольких элементарных правил и следования определенным моделям программирования) обеспечивает повышение производительности и надежности программ, а также делает более понятной их структуру и облегчает их обслуживание. Функции управления потоками весьма напоминают функции управления процессами, так что, например, наряду с функцией GetProcessExitCode существует также функция GetThreadExitCode.
Указанная
• Поскольку потоки разделяют общую память и другие ресурсы, принадлежащие одному процессу, существует вероятность того, что один поток может случайно изменить данные, относящиеся к другому потоку.
• При определенных обстоятельствах вместо улучшения производительности может наблюдаться ее резкое ухудшение.
• Разделение потоками общей памяти и других ресурсов в контексте одного процесса может стать причиной нарушения условий состязаний между процессами и вызывать блокирование некоторых из них.
Некоторых проблем, с которыми действительно приходится сталкиваться, можно избежать, тщательно проектируя и программируя соответствующие задачи, тогда как природа других проблем обусловлена самим параллелизмом, независимо от того, реализуется он путем разбиения процессов на потоки, использованием нескольких процессов или применением специальных методов, например, методов асинхронного ввода/вывода, предоставляемых Windows.
Основные сведения о потоках
В предыдущей главе на рис. 6.1 было показано, каким образом обеспечивается существование потоков в среде процесса. Использование потоков на примере многопоточного сервера, способного обрабатывать запросы одновременно нескольких клиентов, иллюстрирует рис. 7.1; каждому клиенту отведен поток. Эта модель будет реализована в главе 11.
Потоки, принадлежащие одному процессу, разделяют общие данные и код, поэтому очень важно, чтобы каждый поток имел также собственную область памяти, относящуюся только к нему. В Windows удовлетворение этого требования обеспечивается несколькими способами.
• У каждого потока имеется собственный стек, который она использует при вызове функций и обработке некоторых данных.
• При создании потока вызывающий процесс может передать ему аргумент (Arg на рис. 7.1), который обычно является указателем. На практике этот аргумент помещается в стек потока.
• Каждый поток может распределять индексы собственных локальных областей хранения (Thread Local Storage, TLS), а также считывать и устанавливать значения TLS. TLS, описанные далее, предоставляют в распоряжение потоков небольшие массивы данных, и каждый из потоков может обращаться к собственной TLS. Одним из преимуществ TLS является то, что они обеспечивают защиту данных, принадлежащих одному потоку, от воздействия со стороны других потоков.
Рис. 7.1. Потоки в среде сервера
Аргумент
В случае SMP-систем Windows обеспечивает параллельное выполнение различных потоков, в том числе и принадлежащих одному и тому же процессу, на разных процессорах. Правильное использование этой возможности позволяет повысить производительность, однако, как будет показано в двух следующих главах, в результате непродуманных действий без заранее определенной стратегии использования нескольких процессоров производительность SMP-систем может даже ухудшиться по сравнению с однопроцессорными системами.
Управление потоками
Вероятно, вы не будете удивлены, узнав о том, что у потоков, как и у любого другого объекта Windows, имеются дескрипторы и что для создания потоков, выполняющихся в адресном пространстве вызывающего процесса, предусмотрен системный вызов CreateThread. Как и в случае процессов, мы будем говорить иногда о "родительских" и "дочерних" потоках, хотя ОС не делает в этом отношении никаких различий. Системный вызов CreateThread предъявляет ряд специфических требований:
• Укажите начальный адрес потока в коде процесса.
• Укажите размер стека, и необходимое пространство стека будет выделено из виртуального адресного пространства процесса. Размер стека по умолчанию равен размеру стека основного потока (обычно 1 Мбайт). Первоначально для стека отводится одна страница (см. главу 5). Новые страницы стека выделяются по мере надобности до тех пор, пока стек не достигнет своего максимального размера, поэтому не сможет больше расти.
• Задайте указатель на аргумент, передаваемый потоку. Этот аргумент может быть чем угодно и должен интерпретироваться самим потоком.
• Функция возвращает значение идентификатора (ID) и дескриптор потока.
В случае ошибки возвращаемое значение равно NULL.
lpsa — указатель на уже хорошо знакомую структуру атрибутов защиты.
dwStackSize — размер стека нового потока в байтах. Значению 0 этого параметра соответствует размер стека по умолчанию, равный размеру стека основного потока.
lpStartAddr — указатель на функцию (принадлежащую контексту процесса), которая должна выполняться. Эта функция принимает единственный аргумент в виде указателя и возвращает 32-битовый код завершения. Этот аргумент может интерпретироваться потоком либо как переменная типа DWORD, либо как указатель. Функция потока (ThreadFunc) имеет следующую сигнатуру:
lpThreadParm — указатель, передаваемый потоку в качестве аргумента, который обычно интерпретируется потоком как указатель на структуру аргумента.