Чтение онлайн

ЖАНРЫ

Системное программирование в среде Windows

Харт Джонсон М.

Шрифт:

Использование библиотеки С впотоках

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

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

Пример функции strtok показывает, почему при написании некоторых функций библиотеки С не учитывалась многопоточная поддержка. Функция strtok, просматривающая строку в поиске очередного вхождения определенной лексемы, поддерживает сохранение состояния (persistent state) между последовательными вызовами функции, и это состояние хранится в области статической памяти, совместный доступ к которой имеют все потоки, вызывающие эту функцию. 

Microsoft С решает эту проблему, предлагая реализацию библиотеки С под названием LIBCMT.LIB, которая обеспечивает многопоточную поддержку. Однако, это еще не все. Вы не должны использовать функцию CreateThread; для запуска потока и создания специфической для него области рабочей памяти библиотеки LIBCMT.LIB необходимо пользоваться специальной функцией С, а именно, функцией _beginthreadex. Для завершения потока вместо функции ExitThread применяется функция _endthreadex.

Примечание

В качестве упрощенного варианта функции _beginthreadex предусмотрена функция _beginthread, однако использовать ее не рекомендуется. Прежде всего, функция _beginthread не имеет ни атрибутов, ни флагов защиты и не возвращает идентификатор потока. Более того, в действительности она закрывает дескриптор потока, который создает, в результате чего возвращенное значение дескриптора может оказаться недействительным на момент его сохранения родительским потоком. Не следует вызывать и функцию _endthread; она не позволяет пользоваться возвращаемым значением.

Аргументы функции _beginthreadex в точности совпадают с аргументами функций Windows, однако типы данных Windows для этой функции не определены, и поэтому тип возвращаемого значения функции _beginthread необходимо привести к типу HANDLE, что позволит избежать появления предупреждающих сообщений. Убедитесь в том, что определение символической константы _МТ предшествует любому из включаемых файлов; в примерах программ это определение содержится в файле Envirmnt.h. Больше от вас ничего не требуется. Резюмируя, перечислим действия, которые вы должны выполнить, если имеете дело со средой разработки Visual C++.

• Подключите библиотеку LIBCMT.LIB и откажитесь от использования библиотеки, заданной по умолчанию.

• Включите директиву #define _MT во все исходные файлы, в которых используется библиотека С.

• Добавьте включаемый файл <process.h>, содержащий определения функций _beginthreadex и _endthreadex.

• Создайте потоки с помощью функции _beginthreadex; не применяйте для этой цели функцию CreateThread.

• Завершите потоки посредством функции _endthreadex или просто воспользуйтесь оператором return в конце функции потока.

В приложении А вы найдете указания относительно того, как создавать многопоточные приложения. В частности, можно, и даже рекомендуется, указывать библиотеку и определять константу _МТ непосредственно в среде разработки.

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

Библиотеки с многопоточной поддержкой

При проектировании пользовательских библиотек следует уделять самое

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

Еще один пример в главе 12 (программа 12.5) иллюстрирует альтернативный подход, в котором применяется функция DllMain и TLS, описанные далее в настоящей главе.

Пример: многопоточный поиск контекста

В программе 6.1 (grepMP) для выполнения одновременного поиска текстового шаблона в нескольких файлах использовались процессы. Программа 7.1 (grepMT), которая включает исходный код функции поиска текстового шаблона grep, обеспечивает выполнение поиска несколькими потоками в рамках одного процесса. Код функции поиска основан на вызовах функций файлового ввода/вывода библиотеки С. Основная программа аналогична той, которая предлагалась в варианте реализации, основанном на использовании процессов.

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

Мы увидим, однако, что в сочетании с портами завершения ввода/вывода операции асинхронного ввода/вывода становятся очень полезным, а часто и необходимым средством в тех случаях, когда количество потоков очень велико.

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

Наконец, обратите внимание на ограничение в 64 потока, обусловленное значением константы MAXIMUM_WAIT_OBJECTS, которая ограничивает количество дескрипторов при вызове функции WaitForMultipleObjects. Если у вас возникнет необходимость в большем количестве потоков, организуйте для функций WaitForSingleObjects или WaitForMultipleObjects соответствующий цикл.

Предостережение

Программа grepMP осуществляет асинхронный ввод/вывод в том смысле, что отдельные потоки выполняют параллельное синхронное чтение различных файлов, которые блокируются до момента завершения операции чтения. Можно также организовать параллельное чтение одного и того же файла, если у него имеются различные дескрипторы (обычно, по одному дескриптору для каждого потока). Эти дескрипторы должны быть сгенерированы функцией CreateFile, а не функцией DuplicateHandle. В главе 14 описывается асинхронный ввод/вывод, осуществляемый как с использованием, так и без использования пользовательских потоков, а в примере, доступном на Web-сайте (программа atouMT, описанная в главе 14), операции ввода/вывода выполняются с использованием нескольких потоков по отношению к одному и тому же файлу.

Поделиться с друзьями: