Краткие теоретические сведения.
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

Взаимное исключение (mutex) является простейшей формой синхронизации. Оно используется для защиты критической области (critical region), предотвращая одновременное выполнение участка кода несколькими потоками (если взаимное исключение используется потоками) или процессами (если взаимное исключение пользуется несколькими процессами). Выглядит это обычно следующим образом.

блокировать_mutex( );

критическая область

разблокировать_mutex(. );

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

Взаимные исключения по стандарту Posix объявлены как переменные с типом pthread_mutex_t. Если переменная-исключение выделяется статически, ее можно инициализировать константой PTHREAD_MUTEX_INITIALIZER:

static pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;

При динамическом выделении памяти под взаимное исключение (например, вызовом malloc) мы должны инициализировать эту переменную во время выполнения, вызвав функцию pthread_mutex_init.

Следующие три функции используются для установки и снятия блокировки взаимного исключения:

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mptr);

int pthread_mutex_trylock(pthread_mutex_t *mptr);

int pthread_mutex_unlock(pthread_mutex_t *mptr);

Все три возвращают 0 в случае успешного завершения положительное значение Еххх - в случае ошибки.

При попытке заблокировать взаимное исключение, которое уже заблокировано другим потоком, функция pthread_mutex_lock будет ожидать его разблокирования, a pthread_mutex_trylock (неблокируемая функция) вернет ошибку с кодом BUSY.

Одна из классических задач на синхронизацию называется задачей производителя и потребителя. Один или несколько производителей (потоков или процессов) создают данные, которые обрабатываются одним или несколькими потребителями. Эти данные передаются между производителями и потребителями с помощью одной из форм IPC. Схема рассматриваемого примера изображена на рис. 4.1.

Рис. 4.1. Производители и потребитель

В одном процессе имеется несколько потоков-производителей и один поток-потребитель. Целочисленный массив buff содержит производимые и потребляемые данные (данные совместного пользования) Для простоты производители просто устанавливают значение buff[0] в 0, buff[1] в 1 и т. д. Потребитель перебирает элементы массива, проверяя правильность записей. Поток-потребитель не будет запущен, пока все производители не завершат свою работу. Ниже приведена функция main этого примера.

//mutex/prodcons.с

#define MAXNITEMS 1000000

#define MAXNTHREADS 100

int nitems;/*только для чтения потребителем и производителем*/

struct {

pthread_mutex_t mutex;

int buff[MAXNITEMS];

int nput;

int nval;

} shared = {

PTHREAD_MUTEX_INITIALIZER

};

void *produce(void *), *consume(void *);

int main(int argc, char **argv)

{

int i, nthreads count[MAXNTHREADS];

pthread_t tid_produce[MAXNTHREADS], tid_consume;

if (argc != 3)

err_quit("usage: prodcons3 <#items> <#threads>");

nitems = min(atoi(argv[1]), MAXNITEMS).

nthreads = min(atoi(argv[2]) MAXNTHREADS)

/* создание всех производителей и одного потребителя */

for (i=0; i < nthreads; i++) {

count[i] = 0;

pthread_create(&tid_produce[i] NULL, produce. &count[i];

}

pthread_create(&tid_consume, NULL, consume, NULL);

/* ожидание завершения производителей и потребителя */

for (i= 0; i < nthreads; i++) {

pthread_join(tid_produce[i], NULL);

printf("count[%d] =%d\n", i, count[i]);

}

pthread_join(tid_consume, NULL);

exit(0);

}

Программа ожидает завершения работы всех потоков-производителей, выводя содержимое счетчика для каждого потока, и потока-потребителя. Ниже приведен текст функций produce и consume:

//mutex/prodcons.с

void *produce(void *arg)

{

for ( ; ; ) {

pthread_mutex_lock(&shared.mutex);

if (shared.nput >= nitems) {

pthread_mutex_unlock(&shared.mutex):

return(NULL); /* массив полный, готово */

}

shared.buff[shared.nput] = shared.nval;

shared.nput++;

shared nva1++;

pthread_mutex unlock(&shared mutex);

*((int *) arg += 1;

}

}

void consume_wait(int i)

{

for ( ;; ) {

pthread_mutex_lock(&shared.mutex);

if (i <shared.nput) {

pthread_mutex_unlock(&shared.mutex);

return; /* элемент готов */

}

pthread_mutex_unlock(&shared.mutex);

}

}

void *consume(void *arg)

{

int i;

for (i = 0; i < nitems; i++) {

consume_wait(i);

if (shared.buff[i] != i)

printf("buff[%d]=%d\n" i, shared.buff[i]);

}

return(NULL);

}

Методические указания.

3.1. Для уточнения списка заголовочных файлов, необходимых для вызова функций библиотеки pthread используйте инструкции man и info.

3.2. Хотя функции работы с потоками описаны в файле включения pthread.h, на самом деле они находятся в библиотеке. Поэтому процесс компиляции и сборки многопоточной программы выполняется в два этапа, например:

gcc -Wall -c -o test.o test.c

gcc -Wall -o test test.o <path>libgcc.a –lpthread

3.3. Библиотеку libgcc.a рекомендуется скопировать в текущий каталог.

3.4. Для поиска библиотеки средствами файлового менеджера Midnight Commander используйте сочетание клавиш <Alt> - <Shift> - ?

3.5. Для просмотра результата выполнения программы используйте сочетание клавиш <Ctrl> - O. Они работают и в режиме редактирования файла.

3.6. Для протоколирования результатов выполнения программ целесообразно использовать перенаправление вывода с консоли в файл: ./test > result.txt

3.7. Для доступа к файлам, созданным на сервере Linux, применяйте протокол ftp, клиентская программа которого имеется в Windows 2000 и встроена в файловый менеджер FAR. При этом учетная запись и пароль те же, что и при подключении по протоколу ssh.

 

Порядок выполнения работы.

4.1. Для вариантов заданий из лабораторной работы №1 написать и отладить программу, реализующую родительский процесс, вызывающий и отслеживающий состояние порожденных потоков.

4.2. Добавить в написанную программу синхронизацию обращения потоков к какому-либо общему ресурсу, используя взаимные исключения.

 

5. Варианты заданий. См. варианты заданий из лабораторной работы №1

Содержание отчета.

6.1. Цель работы.

6.2. Вариант задания.

6.3. Листинги программ.

6.4. Протоколы выполнения программ.

 

7. Контрольные вопросы.

7.1. Чем потоки отличаются от процессов?

7.2. Функции создания и завершения потоков.

7.3. Зачем и когда необходимо присоединять (join) потоки? Когда и как без этого можно обойтись?

7.4. Атрибуты потоков.

7.5. Что такое мьютекс?

7.6. Особенности инициализации взаимных исключений.

7.7. Чем отличаются функции pthread_mutex_trylock и pthread_mutex_unlock?

7.8. Когда применение мьютексов не гарантирует синхронизации потоков?
Литература

 


Дата: 2019-12-22, просмотров: 206.