|
Программирование >> Структура ядра и системные вызовы
процедуру. Когда поток не может прочитать пользовательский текст, он снимает блокировку чтения-записи и завершается с помошью функции thrjBxit. Поток записи выполняет функцию write. Она читает сообшение из потока чтения и посылает его на стандартное устройство вывода. Для каждого сообшения функция сначала устанавливает блокировку чтения и следит за тем, чтобы значение переменной msglen было больше нуля. Если в переменной msgbuf есть сообшение, поток подает его на стандартное устройство вывода и сбрасывает значение переменной msglen в 0. После этого поток снимает блокировку чтения-записи и передает выполнение потоку чтения, чтобы последний получил доступ к блокировке и поместил следующее сообшение в буфер msgbuf. Если главный поток устанавливает глобальную переменную done в ненулевое значение, значит, сообшений больше нет, и поток записи завершается с помощью функции thr exit. Ниже приведен пример запуска программы pipe2. С и полученные результаты: % СС pipe2.C -Ithread -о pipe2 % pipe2 Have a good day *>Have a good day Bye-Bye *>Bye-Bye read thread (5) exits write thread (4) exits 13.5.4. Семафоры и в библиотеке многопотоковых функций Sun, и в стандарте POSIX.lc имеются API для управления семафорами. Эти построенные на потоках семафоры похожи на семафоры UNIX System V тем, что каждый семафор имеет целочисленное значение, которое должно быть больше или равно 0. Значение семафора может устанавливаться любым потоком, который имеет к нему доступ. Если поток пытается уменьшить значение семафора так, что в результате получится отрицательное число, этот поток блокируется. Он остается в заблокированном состоянии до тех пор, пока другой поток не увеличит значение семафора до нулевого или положительного значения. Блокирующие свойства семафоров можно использовать для синхронизации выполнения определенных сегментов кода и для доступа нескольких потоков к совместно используемым данным. Прежде чем обратиться к разделяемому ресурсу, каждый поток пытается уменьшить значение семафора на единицу. Успеха достигает лишь один из них, а остальные блокируются. После того как этот поток закончил использовать разделяемый ресурс, он увеличивает значение семафора до предыдущего уровня, чтобы другой поток мог разблокироваться и продолжать работу с ресурсом. Семафоры можно использовать вместо взаимоисключающих блокировок и условных переменных. Однако следует отметить, что взаимоисключающие блокировки могут сниматься только потоками, которые ими владеют, тогда как изменять значение семафора может любой поток, имеющий к нему доступ. Обеспечение надежности программ требует дополнительных усилий при программировании, поскольку приходится следить за тем, что именно каждый поток делает с семафором. POSIX.lc использует те же самые API семафоров, которые в POSIX.lb применяются для синхронизации потоков. Описание этих API вы найдете в разделе 10.6. Sun Microsystems использует для синхронизации потоков другой набор API семафоров. В приведенной ниже таблице показаны эти API и соответствующие API семафоров POSIX.lb:
API семафоров Sun имеют следующие прототипы: #include <synch.h> int semajn/t {sema t* svp, int init val, Init type, void* argp); int sema wait(sema t* svp); int sematrywait (sema t* svp); int sema posr (sema t* svp); int semajdestroy (sema t* svp); Значение аргумента svp представляет собой адрес переменной типа semaj. Эта переменная устанавливается как ссылка на семафор посредством функции semajnit. Аргумент type функции semajnit показывает, доступен ли данный семафор для других процессов. Он может принимать следующие значения. Значение аргумента type Смысл USYNC PROCESS USYNC THREAD Семафор может использоваться потоками в других процессах Семафор может использоваться потоками только в вызывающем процессе Аргумент argp функции semajnit в настоящее время не задействуется. Его значение должно быть равно 0. функция sema wait пытается уменьшить значение семафора, указанное в аргументе svp. Эта функция блокирует вызывающий поток до тех пор, пока операция не будет завершена успешно. Функция sematrywait похожа на функцию sema wait, но она неблокирую-i шая. Если эта функция прерывается из-за того, что значение затребованного семафора не может быть уменьшено, она возвращает ненулевое значение и устанавливает егто в EBUSY. * Функция sema post увеличивает значение семафора, заданного аргументом svp. Функция semadestroy уничтожает семафор, указанный аргументом svp. Приведенная ниже программа pipeJ.C - это новая версия программы, представленной в предьщущем разделе. Для синхронизации потоков чтения и записи в новой программе вместо блокировки чтения-записи используется семафор. tinclude <iostream.h> tinclude <thread.h> tinclude <string.h> tinclude <stdio.h> tinclude <signal.h> fdefine FINISH(} { cerr {int}thr self(} thr exit(0}; return 0; } exits\n ; \ sema t semx; int msglen, done; char msgbuf[256]; void* writer (void* argp ),t ( do { sema wait(&semx}; if (msglen) ( cout *> msglen e 0; sema post(&semx); thr yield(); ) while (Idone); FINISH 0; захватить семафор проверить наличие сообщения в буфере msgbuf endl; напечатать сообщение сбросить размер буфера сообщений освободить семафор позволить выполняться другим потокам выполнять, пока не закончатся сообщения завершить поток void* reader (void* argp ) do { sema wait(Ssemx); if (Imsglen) { захватить семафор проверить, пуст ли буфер if (cin.getline(msgbuf,256)) получить новое сообщение msglen = strlen(msgbuf)+1; /* установить размер сообщения в msglen */ ,.if else done = 1; входных сообщений больше нет sema post (&semx); thr yield(); ) while (idone); FINISH 0; освободить семафор позволить выполняться другим потокам выполнять, пока не закончатся сообщения завершить поток main () ( thread t wtid, rtid, tid; /* инициализировать семафор начальным значением 1*/ sema init (&semx, 1, USYNC PROCESS, 0); /* создать поток записи */ if (thr create(О,0,writer,О, О,&wtid)) perror( thr create ); /* создать поток чтения */ if (thr create(О,О,reader,0,0, &rtid)) - perror ( thr create ); f; /* ожидать завершения всех потоков i while (! thrjoin (О, 0,0)) ; /* очистка */ sema destroy(&semx); thr exit(0); return 0; Эта программа аналогична двум предыдущим и отличается только тем, что вместо взаимоисключающей блокировки, условной переменной и блокировки чтения-записи используется семафор. Этот семафор, semx, инициализируется в главном потоке начальным значением 1. При запуске и поток чтения, и поток записи пытаются захватить этот семафор вызовом функции semawait, но успеха может достичь только один. Если поток записи захватывает семафор первым, он обнаруживает, что буфер сообщений пуст, и освобождает семафор, передавая право выполнения потоку чтения. Захватив семафор, поток чтения читает сообщение пользователя и помещает его в массив msgbuf. Затем он присваивает переменной msglen значение, равное размеру текста сообщения. После этого поток чтения освобождает семафор и передает право выполнения потоку записи. Разблокированный поток записи обнаруживает, что буфер msgbuf ш пуст, и направляет содержащееся в нем сообщение на стандартное устройство вывода. Затем он сбрасывает переменную msglen и освобождает семафор. Начинается следующий цикл обработки сообщений потоком чтения и потоком записи. Если поток чтения не может прочитать сообщение (возможно, из-за признака конца файла), он устанавливает переменную done в 1, сообщая таким образом, что и поток записи, и поток чтения должны завершиться посредством вызова функции thrjexit. Ниже приведен пример запуска программы ргреЗ.Си показаны полученные результаты: % СС pipe3.C -Ithread -о pipeS % pipe3 Have a good day *>Have a good day Bye-Bye *>Bye-Bye 5 exits Л exits 13.6. Данные потоков Переменными и памятью, динамически выделяемой в функции, владеют все потоки, которые эту функцию выполняют. Глобальные и статические переменные могут совместно использоваться множеством потоков, но для синхронизации доступа к таким общим данным потоки должны применять взаимоисключающие блокировки, условные переменные и т.д. Однако некоторые глобальные переменные нельзя синхронизировать. Например, переменная errno, определенная в библиотеке С, устанавливается кавдым системным вызовом. Если несколько потоков одновременно выполняют системные вызовы, то переменная errno для разных потоков должна устанавливаться в разные значения. Эту проблему можно преодолеть, потребовав, чтобы только один поток выполнял системный вызов в каждый момент времени. В результате многопотоковые программы будут работать в однопо-токовом режиме. Проблема с переменной errno разрешается библиотекой потоков, которая автоматически создает частную копию errno для каждого потока, выполняющего системные вызовы. Таким образом, несколько потоков могут одновременно выполнять системные вызовы и проверять собственные значения переменной errno. Подобная проблема возникает и в ситуации, когда функции содержат статические переменные, к которым одновременно обращаются несколько потоков. Например, библиотечная С-функция ctime возвращает символьную строку местного времени и даты. Эта строка хранится во внутреннем статическом буфере функции ctime: const char* ctime (const time t *timval) ( - static char timbuf[. . . ]; /* конвертировать timval в местную дату и время и занести результат в timbuf.*/ return timbuf; Таким образом, если несколько потоков вызывают эту функцию одновременно, она должна суметь возвратить разным потокам разные результаты. Эту проблему можно было бы разрешить, динамически выделяя буферы для хранения затребованной даты и времени в каждом вызове, однако такой подход сопряжен с рядом трудностей, а именно: время выполнения функции и объем необходимой для нее памяти увеличиваются; в результате производительность программ, использующих эту функцию, падает, а требования к памяти возрастают; существующие программы, которые используют эту функцию, необходимо исправить так, чтобы динамически выделяемая память, занимаемая этой функцией, освобождалась; неисправленные однопотоковые программы нельзя использовать в многопотоковой среде. Чтобы преодолеть эти трудности, POSIX.lc и Sun Microsystems определяют реентерабельные версии популярных библиотечных функций. Эти новые функции могут одновременно вызываться множеством потоков, при этом побочные эффекты отсутствуют, а производительность остается такой же, как у однопотоковых версий, и даже может повыситься. Имена этих реентерабельных функций отличаются от имен аналогичных однопотоковых версий суффиксом г. Так, реентерабельная версия функции ctime называется ctime r. Все эти новые функции принимают один дополнительный аргумент - адрес переменной, содержащей возвращаемое значение. Эта переменная определяется вызывающими потоками. Таким образом, несколько потоков могут одновременно вызвать одну и ту же функцию, и каждый из них получит ответ через соответствующую переменную. Однопотоковые и существующие многопотоковые программы могут продолжать пользоваться старыми библиотечными функциями, и многопотоковая среда их не касается. Новые многопотоковые программы должны использовать реентерабельные версии библиотечных функций. Новая функция ctimej- определяется следующим образом: char* ctime r (const time t* timval, char buf[J) { /* конвертировать timval в местную дату и время и занести результат в buf */ return buf; Отметим, что функция ctime r использует свой входной аргумент buf ддя хранения затребованных вызывающим потоком значений даты и времени и возвращает этому потоку адрес buf Фирма Sun Microsystems создала реентерабельные версии библиотечных функций С, библиотечных функций работы с гнездами и математических библиотечных функций. Даже при наличии реентерабельных функций и динамически определяемьгх для каждого потока глобальных переменных, поддерживаемых библиотекой
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |