|
Программирование >> Структура ядра и системные вызовы
Помимо этих преимуществ, многопотоковое программирование - хоро-щее дополнение к объектно-ориентированному программированию. Это обусловлено тем, что каждое объектно-ориентированное приложение состоит из набора объектов, взаимодействующих друг с другом для выполнения задач. Каждый из этих объектов - независимый компонент, который может выполняться потоком и запускаться параллельно с другими объектами, что позволяет значительно повысить производительность таких приложений. Например, в многопотоковом объектно-ориентированном оконном интерфейсе каждое меню, кнопка, текстовое поле и прокручиваемое окно может обрабатываться потоком. Следовательно, любое количество этих оконных объектов можно активизировать друг за другом, не ожидая заверщения обработки других объектов. Таким образом, GUI-приложение в целом легче реагирует на действия пользователя, интерактивнее , чем его одаопотоко-вый вариант. Потоки выполнения отличаются от порожденных процессов, создаваемых функцией API fork: Потоками выполнения можно управлять либо с помощью библиотечных функций пользовательского уровня, либо с помощью ядра операционной системы. Процессами, которые порождаются системным вызовом fork, управляет ядро операционной системы. Вообще говоря, потоки более эффективны и требуют гораздо меньще внимания со стороны ядра в процессе создания и управления. Все потоки выполнения в процессе совместно используют сегменты данных и кода. Порожденный процесс имеет собственную копию виртуального адресного пространства, отдельную от родительского процесса. Таким образом, потоки используют гораздо меньше системных ресурсов, чем порожденные процессы. Функции exit или exec, вызываемые потоком, завершают все потоки в этом процессе. Если же эти функции вызывает порожденный процесс, то на родительский процесс ее действие не распространяется. Если поток модифицирует в процессе какую-то глобальную переменную, эти изменения видимы для остальных потоков этого процесса. Поэтому для потоков, обращающихся к совместно используемым данным, необходима синхронизация. Во взаимоотношениях между порожденным и родительским процессами эта проблема не возникает. Теперь перечислим преимущества многопотокового программирования: повышается производительность процессов и ускоряется реакция на действия пользователя; процесс может использовать все свободные аппаратные средства многопроцессорной системы, в которой вьшолняется многопотоковое приложение; j программистам могут структурировать код в независимо выполняемые . компоненты; снижается необходимость использования функции fork для создания порожденных процессов и таким образом увеличивается производительность каждого процесса (реже выполняется переключение контекста); в Л управлении вьтолнением потоков в меньшей степени участвует ядро системы; i й многопотоковое программирование - оптимальный способ повышения * производительности объектно-ориентированных приложений, рассчи- тайных на использование в многопроцессорных системах. Недостаток многопотокового программирования состоит в том, что пользователи должны обеспечить синхронизацию потоков в каждой программе. Синхронизация нужна для того, чтобы потоки не делали случайных ошибок при чтении и записи совместно используемых данных и не могли уничтожить свой процесс системным вызовом exit или exec. Разработка технологии многопотокового программирования началась в середине 80-х годов. Поставщики разных вариантов ОС UNIX предлагали различные версии интерфейсов многопотокового программирования. Комитет POSIX разработал комплект многопотоковых API, который вошел в стандарт POSIX.lc. В этой главе будут рассмотрены и многопотоковые API ОС Solaris 2.x (разработка фирмы Sun Microsystems), и многопотоковые API стандарта POSIX.lc, но особое внимание мы уделим API фирмы Sun. Причина этого в том, что стандарт POSIX.lc - новый и его пока поддерживает не очень много поставщиков UNIX, а вот многопотоковые API фирмы Sun доступны прикладным программистам уже довольно давно. К тому же многопотоковые API Sun имеют близкое сходство с многопотоковыми API POSIX.lc. Благодаря этому приложения, построенные на API Sun, можно свободно конвертировать в стандарт POSIX.lc. 13.1. Структура и методика использования потоков выполнения Поток выполнения состоит из следующих элементов: , идентификатора потока; ,; динамического стека; .) , набора регистров (счетчик команд, указатель стека); £ сигнальной маски; значения приоритета; специальной памяти. Поток выполнения создается функцией thr create (в POSIX.lc - pthread create). Каждому потоку присваивается идентификатор, уникальный среди всех потоков процесса. Вновь созданный поток наследует сигнальную маску процесса, динамический стек, значение приоритета и набор регистров. Динамический стек и регистры (счетчик команд и указатель стека) позволяют потоку выполняться независимо от других потоков. Поток может изменить унаследованную сигнальную маску и выделить динамическую память для хранения своих данных. vy Потоку при создании назначается соответствующая функция. Поток завершается, когда эта назначенная функция возвращает результат или когда поток вызывает функцию thr exit (в POSIX. 1с - pthreadjexit). Когда в процессе создается первый поток, то фактически создаются два потока: один - для выполнения указанной функции, а другой - для продолжения выполнения процесса. Последний поток завершается, когда функция main возвращает результат или он сам вызывает функцию threxit. Все потоки в процессе совместно используют одни сегменты данных и кода. Когда один поток записывает данные в глобальные переменные в процессе, остальные потоки сразу же видят эти изменения. Если какой-либо поток вызывает API exit или API exec, то завершаются все потоки и сам процесс. Поэтому завершающийся поток, если он не хочет разрушить процесс, в котором выполняется, должен вызывать функцию threxit. Поток может изменить свою сигнальную маску с помощью функции thr sigsetmask (в POSIX. 1с - pthreadsigmask). Сигнал, передаваемый процессу, получат все потоки, которые не замаскировали его. Поток может посылать сигналы в другие потоки этого же процесса, используя функцию thrkill (в POSIX. 1с - pthread kill), но не может посьшать сигналы в потоки другого процесса, так как уникальность идентификаторов потоков не распространяется на другие процессы. Для настройки собственного механизма обработки сигналов поток может использовать API signal или sigaction. Потовсу присваивается целочисленное значение приоритета. Чем больше это значение, тем чаще планируемое вьшолнение потока. Значение приоритета потока можно запросить с помощью функции thr getprio и изменить с помощью функции thrsetprio (в POSIX. 1с - pthread attr getschedparam и pthreadattrsetschedparam соответственно). Поток может намеренно передать вьшолнение другим потокам с таким же приоритетом; для этого используется функция thr yield (в POSIX. 1с - sched yield). Кроме того, поток может ожидать завершения другого потока и получить его код возврата с помощью функции thrJoin (в POSIX. 1с - pthreadJoin). В API фирмы Sun поток может, пользуясь функциями thrsuspend и thrcontinue, приостанавливать и возобновлять вьшолнение другого потока. Если какая-то функция выполняется множеством потоков и содержит статические или глобальные переменные которые используются разными потоками, нужно создать специальную память для хранения этих фактических данных по каждому потоку. Эта специальная память выделяется с помощью функций thrkeycreate, thrsetspecific и thr getspecific. 13.2. Потоки и облегченные процессы Многопотоковые библиотечные функции, разработанные фирмой Sun Microsystems, создают облегченные процессы (lightweight processes, LWP), выполнение которых планируется ядром. Такие процессы похожи на виртуальные процессоры тем, что многопотоковые библиотечные функции управляют выполнением потоков в процессе, связывая их с LWP. Если связанный с LWP поток приостанавливается (например, функцией thr yield или thr suspend), этот LWP можно связать с другим потоком, функцию которого он должен будет выполнять. Если LWP выполняет системный вызов от имени потока, он остается связанным с этим потоком до возврата из вызова. Если все LWP, связанные с потоками, заблокированы системными вызовами, многопотоковые библиотечные функции создают новые LWP, с которыми могут быть связаны потоки, ожидающие выполнения. Таким образом обеспечивается непрерывность выполнения процесса. Наконец, если LWP больше, чем потоков в процессе, то в целях экономии системных ресурсов многопотоковые библиотечные функции удаляют лишние LWP. Большинство потоков не связаны, поэтому их можно связать с любыми свободными LWP. Вместе с тем процесс может создать один и более потоков, постоянно связанных с облегченными процессами. Эти потоки называются связанными потоками. Такое связывание потоков используется главным образом в тех случаях, когда потоки должны: планироваться ядром для обработки в режиме реального времени; иметь собственные альтернативные сигнальные стеки; иметь собственные будильники и таймер. Взаимосвязь потоков, облегченных процессов и аппаратных процессоров показана на рис. 13.1. Процесс 123 Процесс 6231 Процесс 251 Пользователь Ядро Аппаратные средства Условные обозначения облегченный процесс несвязанный поток связанный аппаратный поток процессор Рис. 13.1. Взаимосвязи при планировании потоков, облегченных процессов и аппаратных процессоров На рис. 13.1 в процессе 123 есть два несвязанных потока, которые планируются на два LWP. В процессе 6231 - три несвязанных потока, которые планируются на два LWP, и один связанный, который вьшолняется третьим LWP. В процессе 251 - четыре несвязанных потока, которые планируются на один LWP. Несвязанные потоки в каждом процессе планируются многопотоковыми библиотечными функциями к связыванию с LWP и вьшолнению в этом процессе. LWP всех процессов, в свою очередь, плани-руютх:я ядром к вьшолнению на трех имеюцщхся аппаратных процессорах. В POSIX.lc у потоков есть атрибут, который называется областью действия конкуренции при планировании (scheduling contention scope). Если этот атрибут установлен в PTHREAD SCOPE PROCESS, то данным потоком управляют библиотечные функции пользовательского уровня и он является несвязанным . Все потоки с этим атрибутом совместно используют ресурсы процессора, доступные для содержащего их процесса. Если же вышеупомянутый атрибут установлен в PTHREAD SCOPE SYSTEM, то данным потоком управляет ядро операционной системы и он считается связанным . В стандарте POSIX. 1с не указано, как ядро должно обрабатывать связанный поток. 13.3. API потоков выполнения фирмы Sun Microsystems В этом разделе рассматриваются только API потоков выполнения, разработанные фирмой Sun Microsystems. API потоков выполнения стандарта POSIX.lc будут рассмотрены в разделе 13.4. Мы обсуждаем эти потоки выполнения отдельно, чтобы не запутать читателей. Прочитав раздел 13.4, вы узнаете о соответх:твии API Sun и POSIX.lc, благодаря которому многопотоковые приложения можно преобразовывать из формата Sun в стандарт POSIX.lc. Чтобы использовать API потоков выполнения фирмы Sun, необходимо сделать следующее: включить в программу заголовок <thread.h>; откомпилировать и скомпоновать программу с опцией -Ithread. Если указывается опция -1С, то опцию -Ithread нужно поместиггь перед ней. Например, следующая команда компилирует многопотоковую С++-про-грамму X. С: % СС х.С -о X -Ithread -1С Если не указано иного, то большинство описанных ниже API при успешном завершении возвращают О, а в случае неудачи--1. В последнем случае может вызываться функция perror, которая выводит сообщения об ошибках. 13.3.1. функция thr create Прототип функции thr create выглядит следующим образом: #include <thread.h> int thr create (void* stackp, size t stack size, void* (*funcp)(void*), void* argp, long flags, thread t* tid p); Эта функция создает новый поток для вьшолнения функции, адрес которой задан аргументом funcp. Функция, указанная в этом аргументе, должна принимать один входной аргумент типа void* и возвращать данные такого же типа. Фактический аргумент, который передается функции/млср, когда начинает вьшолняться новый поток, указывается в аргументе a?gp. Аргументы stackp и stack size задают адрес определяемой пользователем области памяти и ее размер в байтах. Эта память используется в качестве динамического стека нового потока. Если аргумент stackp принимает значение NULL, функция выделяет область стека размером stack size байтов. Если значение stack size равно нулю, функция использует стандартное системное значение, а именно - один мегабайт виртуальной памяти. Пользователям очень редко приходится собственноручно вьщелять область памяти для стека потока. Таким образом, аргументы stackp и stack size обычно принимают значения NULL и нуль соответственно. Аргумент flags может иметь нулевое значение. В таком случае новому потоку не нужно присваивать какие-либо специальные атрибуты. Значение аргумента У7а5 может состоять из одного или нескольких следующих битовых флагов: Значение аргумента ffags thr detached thr suspended thr bound l;thr new lwp ?hr daemon Смысл Создает отсоединенный поток. Это означает, что когда поток завершается, все его ресурсы и присвоенный ему идентификатор можно использовать повторно для другого потока. Ни один поток не должен ожидать его завершения (через функцию thr Join) Приостанавливает выполнение нового потока до тех пор, пока другой поток не вызовет функцию thrcontinue, которая позволит возобновить его выполнение Создает постоянно связанный поток Создает новый lwp вместе с новым потоком Создает новый поток-демон. Как правило, многопотоковый процесс завершается, когда завершаются все его потоки. Если же процесс содержит один или несколько потоков-демонов, то он завершается сразу же по завершении всех его потоков, которые не являются демонами
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |