Читаем Системное программирование в среде Windows полностью

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

Задача инкрементирования N, представленная на рис. 8.1, может быть реализована посредством единственной строки кода:

InterlockedIncrement(&N);

N — это целое число типа long со знаком, и функция возвращает его новое значение, несмотря на то что другой поток мог изменить значение N еще до того, как поток, вызвавший функцию InterlockedIncrement, успеет воспользоваться возвращенным значением.

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

Локальная и глобальная память

Суть другого требования, предъявляемого к корректному многопоточному коду, состоит в том, что глобальная память не должна использоваться для локальных целей. Так, применение функции ThFunc, приводившейся ранее в качестве примера, будет необходимым и уместным в тех случаях, когда поток должен располагать собственным экземпляром N. N может быть использовано для хранения временных результатов или размещения аргумента функции. Если же N размещается в глобальной памяти, то все процессы будут разделять единственный экземпляр N, что может стать причиной некорректного поведения программы, как бы тщательно вы ни планировали синхронизацию доступа к этой переменной. Ниже приводится пример подобного некорректного использования N. N должно быть локальной переменной, размещаемой в стеке функции потока.

DWORD N;

DWORD WINAPI ThFunc (TH_ARGS pArgs) {

 …

 N = 2 * pArgs->Count; …

}

Резюме: безопасный многопоточный код

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

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

2. В тех случаях, когда функцию могут вызывать несколько потоков, а какой-либо специфический для состояния потока параметр, например счетчик, должен сохранять свое значение в течение промежутков времени, отделяющих один вызов функции от другого, значение параметра состояния должно храниться в TLS или в структуре данных, выделенной специально для этого потока, например, в структуре данных, передаваемой потоку при его создании. Использовать стек для сохранения постоянно хранимых (persistent) значений не следует. Применение необходимой методики при построении безопасных многопоточных DLL иллюстрируют программы 12.4 и 12.5.

3. Старайтесь не создавать предпосылок для формирования условий состязаний наподобие тех, которые возникли бы в программе 7.2 (sortMT), если бы потоки не создавались в приостановленном состоянии. Если предполагается, что в определенной точке программы должно выполняться некоторое условие, используйте ожидание объекта синхронизации для гарантии того, что, например, дескриптор всегда будет ссылаться на существующий поток.

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

5. Переменные, разделяемые всеми потоками, должны быть статическими или храниться в глобальной памяти, объявленной с использованием спецификатора volatile, а также должны быть защищены с использованием описанных ниже механизмов синхронизации.

Объекты синхронизации обсуждаются в следующем разделе. Приведенных в нем объяснений вам будет достаточно для того, чтобы разработать простой пример системы "производитель/потребитель" (producer/consumer).

Объекты синхронизации потоков

До сих пор нами были обсуждены только два механизма, обеспечивающие синхронизацию процессов и потоков друг с другом:

1. Поток, выполняющийся в контексте одного процесса, может дожидаться завершения другого процесса с использованием функции ExitProcess путем применения к дескриптору процесса функций ожидания WaitForSingleObject или WaitForMultipleObject. Тем же способом поток может организовать ожидание завершения (с помощью функции ExitThread или выполнения оператора return) другого потока.

2. Блокировки файлов, предназначенные для частного случая синхронизации доступа к файлам.

Перейти на страницу:

Похожие книги

C++: базовый курс
C++: базовый курс

В этой книге описаны все основные средства языка С++ - от элементарных понятий до супервозможностей. После рассмотрения основ программирования на C++ (переменных, операторов, инструкций управления, функций, классов и объектов) читатель освоит такие более сложные средства языка, как механизм обработки исключительных ситуаций (исключений), шаблоны, пространства имен, динамическая идентификация типов, стандартная библиотека шаблонов (STL), а также познакомится с расширенным набором ключевых слов, используемым в .NET-программировании. Автор справочника - общепризнанный авторитет в области программирования на языках C и C++, Java и C# - включил в текст своей книги и советы программистам, которые позволят повысить эффективность их работы. Книга рассчитана на широкий круг читателей, желающих изучить язык программирования С++.

Герберт Шилдт

Программирование, программы, базы данных
Programming with POSIX® Threads
Programming with POSIX® Threads

With this practical book, you will attain a solid understanding of threads and will discover how to put this powerful mode of programming to work in real-world applications. The primary advantage of threaded programming is that it enables your applications to accomplish more than one task at the same time by using the number-crunching power of multiprocessor parallelism and by automatically exploiting I/O concurrency in your code, even on a single processor machine. The result: applications that are faster, more responsive to users, and often easier to maintain. Threaded programming is particularly well suited to network programming where it helps alleviate the bottleneck of slow network I/O. This book offers an in-depth description of the IEEE operating system interface standard, POSIX (Portable Operating System Interface) threads, commonly called Pthreads. Written for experienced C programmers, but assuming no previous knowledge of threads, the book explains basic concepts such as asynchronous programming, the lifecycle of a thread, and synchronization. You then move to more advanced topics such as attributes objects, thread-specific data, and realtime scheduling. An entire chapter is devoted to "real code," with a look at barriers, read/write locks, the work queue manager, and how to utilize existing libraries. In addition, the book tackles one of the thorniest problems faced by thread programmers-debugging-with valuable suggestions on how to avoid code errors and performance problems from the outset. Numerous annotated examples are used to illustrate real-world concepts. A Pthreads mini-reference and a look at future standardization are also included.

David Butenhof

Программирование, программы, базы данных