26.1. Введение

We use cookies. Read the Privacy and Cookie Policy

26.1. Введение

Согласно традиционной модели Unix, когда процессу требуется, чтобы некое действие было выполнено каким-либо другим объектом, он порождает дочерний процесс, используя функцию fork, и этим порожденным процессом выполняется необходимое действие. Большинство сетевых серверов под Unix устроены именно таким образом, как мы видели при рассмотрении примера параллельного (concurrent) сервера: родительский процесс осуществляет соединение с помощью функции accept и порождает дочерний процесс, используя функцию fork, а затем дочерний процесс занимается обработкой клиентского запроса.

Хотя эта концепция с успехом использовалась на протяжении многих лет, с функцией fork связаны определенные неудобства.

? Стоимость функции fork довольно высока, так как при ее использовании требуется скопировать все содержимое памяти из родительского процесса в дочерний, продублировать все дескрипторы и т.д. Текущие реализации используют технологию, называемую копированием при записи (copy-on-write), при которой копирование пространства данных из родительского процесса в дочерний происходит лишь тогда, когда дочернему процессу требуется своя собственная копия. Но несмотря на эту оптимизацию, стоимость функции fork остается высокой.

? Для передачи данных между родительским и дочерним процессами после вызова функции fork требуется использовать средства взаимодействия процессов (IPC). Передача информации перед вызовом fork не вызывает затруднений, так как при запуске дочерний процесс получает от родительского копию пространства данных и копии всех родительских дескрипторов. Но возвращение информации из дочернего процесса в родительский требует большей работы.

Обе проблемы могут быть разрешены путем использования программных потоков (threads). Программные потоки иногда называются облегченными процессами (lightweight processes), так как поток проще, чем процесс. В частности, создание потока требует в 10–100 раз меньше времени, чем создание процесса.

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

Однако общими становятся не только глобальные переменные. Все потоки одного процесса разделяют:

? инструкции процесса;

? большую часть данных;

? открытые файлы (например, дескрипторы);

? обработчики сигналов и вообще настройки для работы с сигналами (действие сигнала);

? текущий рабочий каталог;

? идентификаторы пользователя и группы пользователей.

У каждого потока имеются собственные:

? идентификатор потока;

? набор регистров, включая счетчик команд и указатель стека;

? стек (для локальных переменных и адресов возврата);

? переменная errno;

? маска сигналов;

? приоритет.

ПРИМЕЧАНИЕ

Как сказано в разделе 11.18, можно рассматривать обработчик сигнала как некую разновидность потока. В традиционной модели Unix у нас имеется основной поток выполнения и обработчик сигнала (другой поток). Если в основном потоке в момент возникновения сигнала происходит корректировка связного списка и обработчик сигнала также пытается изменить связный список, обычно начинается путаница. Основной поток и обработчик сигнала совместно используют одни и те же глобальные переменные, но у каждого из них имеется свой собственный стек.

В этой книге мы рассматриваем потоки POSIX, которые также называются Pthreads (POSIX threads). Они были стандартизованы в 1995 году как часть POSIX.1c и будут поддерживаться большинством версий Unix. Мы увидим, что все названия функций Pthreads начинаются с символов pthread_. Эта глава является введением в концепцию потоков, необходимым для того, чтобы в дальнейшем мы могли использовать потоки в наших сетевых приложениях. Более подробную информацию вы можете найти в [15].

Данный текст является ознакомительным фрагментом.