Кафедра вт отчет по лабораторной работе №3 по дисциплине «Операционные системы» Подсистема управления потоками студент гр. 8В53 Чернявский Е. И. Проверил: доцент каф. Вт




Скачать 109.41 Kb.
Дата 27.08.2016
Размер 109.41 Kb.
ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ
Государственное образовательное учреждение высшего профессионального образования
«ТОМСКИЙ ПОЛИТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ»
Факультет автоматики и вычислительной техники
Кафедра ВТ



Отчет по лабораторной работе №3

по дисциплине «Операционные системы»
Подсистема управления потоками

Выполнил:

студент гр. 8В53

Чернявский Е.И.
Проверил:

доцент каф. ВТ

Шерстнев В.С.

Томск 2008


Цель работы:


Ознакомиться с подсистемой управления потоками в ОС Unix и основными программными средствами для создания, управления и удаления потоков.
Задание:

Изучить основные программные средства управления потоками ОС Unix, а также способы синхронизации потоков с использованием мьютексов. Разработать приложения для многопоточных вычислений с использованием синхронизации посредством мьютексов, семафоров и условных переменных:



  1. Ознакомиться с теоретическим материалом.

  2. Разработать три многопоточные программы с использованием минимум двух потоков и различных средств синхронизации. Например: два потока записывают и читают информацию из одного файла; два потока увеличивают значение общей переменной; два потока с различной частотой считывают и записывают данные в общий буфер памяти, два потока поочередно записывают данные в один файл и т. п. (т. е. обеспечить синхронизированную работу двух потоков в критической секции).

  3. Синхронизацию потоков обеспечить с использованием мьютексов, семафоров, условных переменных.



Порядок работы:


Задание 1. Ознакомиться с теоретическим материалом.

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

По категории реализации потоки делятся на пользовательские (реализуемые через специальные библиотеки потоков) и уровня ядра (реализуемые через системные вызовы).

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

Основное отличие процесса от потока заключается в способе использования системных ресурсов. Дочерний процесс практически независим от родительского, для него системой выделяется отдельное адресное пространство, и он на равных правах с родительским процессом «конкурирует» за процессорное время. При этом можно уничтожить родительский процесс, не затронув дочерний, который может выполняться и после завершения родительского процесса. В отличие от дочернего процесса, поток, порожденный данным процессом, полностью зависим от процесса и завершение процесса влечет уничтожение всех созданных им потоков, поскольку происходит освобождение системных ресурсов, выделенных для этого процесса. Еще одним отличием является невозможность изменить права доступа для потока, тогда как дочерний процесс в редких случаях (например, при смене пароля для входа в систему) может обладать правами доступа отличными от прав родительского процесса.

В Linux каждый поток на самом деле является процессом, и для того, чтобы создать новый поток, нужно создать новый процесс. Однако для создания дополнительных потоков используются процессы особого типа. Эти процессы представляют собой обычные дочерние процессы главного процесса, но они разделяют с главным процессом адресное пространство, файловые дескрипторы и обработчики сигналов. Для обозначения процессов этого типа применяется специальный термин – легкие процессы (lightweight processes). Поскольку для потоков не требуется создавать собственную копию адресного пространства (и других ресурсов) своего процесса-родителя, создание нового легкого процесса требует значительно меньших затрат, чем создание полновесного дочернего процесса.

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


  • pthread_create() – создание потока;

  • pthread_join() – блокирование работы вызывающего процесса (потока), ожидание завершения;

  • pthread_cancel() – досрочное завершение потока из другого потока или процесса;

  • pthread_exit() – завершает поток, код завершения передается функции.

Функция pthread_join() подобна exit(), однако вызов exit() в «основном» процессе программы приведет к завершению всей программы.

При выполнении нескольких потоков во многих случаях необходимо синхронизировать их взаимодействие. Существует несколько способов синхронизации потоков:

• Взаимные исключения – мьютексы (Mutex – MUTial EXclusion);

• Переменные состояния;

• Семафоры.

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

Использование мьютексов гарантирует, что только один поток в некоторый момент времени выполняет критическую секцию кода. Мьютексы можно использовать и в однопоточном коде. Доступны следующие действия с мьютексом: инициализация, удаление, захват или открытие, попытка захвата. Объекты синхронизации являются переменными в памяти, к которым можно обратиться так же, как к данным. Потоки в различных процессах могут связаться друг с другом через объекты синхронизации, помещенные в разделяемую память потоков, даже в случае, когда потоки в различных процессах невидимы друг для друга. Объекты синхронизации можно также разместить в файлах, где они будут существовать независимо от создавшего их процесса.

Функции синхронизации потоков с использованием мьютексов. Для синхронизации потоков с использованием мьютексов используются следующие основные функции:


  • pthread_mutex_init() – инициализирует взаимоисключающую блокировку;

  • pthread_mutex_destroy() – удаляет взаимоисключающую блокировку;

  • pthread_mutex_lock() – устанавливает блокировку. В случае, если блокировка была установлена другим потоком, текущий поток останавливается до снятия блокировки другим процессом;

  • pthread_mutex_unlock() – снимает блокировку.

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

Функция int pthread_mutexattr_init (pthread_mutexattr_t* mattr) используется, чтобы инициализировать объект атрибутов mattr значениями по умолчанию. Память для каждого объекта атрибутов выделяется системой поддержки потоков во время выполнения.

Иногда может возникнуть необходимость одновременного доступа к нескольким ресурсам. При этом возникает проблема, заключающаяся в том, что два потока пытаются захватить оба ресурса, но запирают соответствующие мьютексы в различном порядке. Наилучшим способом избежать проблем является запирание нескольких мьютексов в одном и том же порядке во всех потоках. Эта техника называется иерархией блокировок: мьютексы упорядочиваются путем назначения каждому своего номера. После этого придерживаются правила – если мьютекс с номером n уже заперт, то нельзя запирать мьютекс с номером, меньше n. Если блокировка всегда выполняется в указанном порядке, тупик не возникнет. Однако, эта техника может использоваться не всегда поскольку иногда требуется запирать мьютексы в порядке, отличном от порядка их номеров.

Чтобы предотвратить тупик в этой ситуации, лучше использовать функцию pthread_mutex_trylock(). Один из потоков должен освободить свой мьютекс, если он обнаруживает, что может возникнуть тупик.



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

Семафор (S) – это защищенная переменная, значения которой можно опрашивать и менять только при помощи специальных операций P(S) и V(S) и операции инициализации. Семафор может принимать целое неотрицательное значение. При выполнении потоком операции P над семафором S значение семафора уменьшается на 1 при S>0, или поток блокируется, «ожидая на семафоре», при S=0. При выполнении операции V(S) происходит пробуждение одного из потоков, ожидающих на семафоре S, а если таковых нет, то значение семафора увеличивается на 1. В простом случае, когда семафор работает в режиме 2-х состояний (S>0 и S=0), его алгоритм работы полностью совпадает с алгоритмом мьютекса.

Как следует из вышесказанного, при входе в критическую секцию поток должен выполнять операцию P(S), а при выходе из критической секции операцию V(S).

Синхронизация с использованием условной переменной. Условная переменная позволяет потокам ожидать выполнения некоторого условия (события), связанного с разделяемыми данными. Над условными переменными определены две основные операции: информирование о наступлении события и ожидание события. При выполнении операции «информирование» один из потоков, ожидающих значения условной переменной, возобновляет свою работу.

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



Прототипы функций для работы с условными переменными содержатся в файле pthread.h:

  • pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* attr) – инициализирует условную переменную cond с указанными атрибутами attr или с атрибутами по умолчанию (при указании 0 в качестве attr).

  • int pthread_cond_destroy(pthread_cond_t* cond) – уничтожает условную переменную cond.

  • int pthread_cond_signal(pthread_cond_t* cond) – информирование о наступлении события потоков, ожидающих на условной переменной cond.

  • int pthread_cond_broadcast(pthread_cond_t* cond) – информирование о наступлении события потоков, ожидающих на условной переменной cond. При этом возобновлены будут все ожидающие потоки.

  • int pthread_cond_wait(pthread_cond_t* cond , pthread_mutex_t* mutex) – ожидание события на условной переменной cond.


Задание 2. Разработать три многопоточные программы с использованием минимум двух потоков и различных средств синхронизации. Например: два потока записывают и читают информацию из одного файла; два потока увеличивают значение общей переменной; два потока с различной частотой считывают и записывают данные в общий буфер памяти, два потока поочередно записывают данные в один файл и т. п. (т. е. обеспечить синхронизированную работу двух потоков в критической секции).
1. Синхронизация с помощью мьютексов.
В данной программе присутствуют два потока: в первом потоке происходит запись последовательности чисел в файл, а во втором – последовательное их чтение и перемножение для расчета факториала введенного числа.

#include


#include

#include
ofstream output;

ifstream input;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
const char* filename = "file.dat";

int n = 1;
void* Nums( void* arg ); // deals with numbers that factorial is then made of

void* Fact( void* arg ); // count the sum and print out the factorial of n
int main() {

pthread_t NumsThread, FactThread;

cout

cin >> n;

pthread_create( &NumsThread, NULL, Nums, NULL );

pthread_create( &FactThread, NULL, Fact, NULL );
pthread_join( NumsThread, NULL );

pthread_join( FactThread, NULL );
pthread_mutex_destroy( &mutex );

}
void* Nums( void* arg ) {

for( int i = 1; i

pthread_mutex_lock( &mutex );

output.open( filename );

output

output.close();

pthread_mutex_unlock( &mutex );

sleep( 1 );

}

}
void* Fact( void* arg ) {

int num, fact = 1;

cout

for( int i = 0; i

pthread_mutex_lock( &mutex );

input.open( filename );

input >> num;

cout

fact *= num;

input.close();

pthread_mutex_unlock( &mutex );

sleep( 1 );

}

cout

}
С помощью простейшего makefile скомпилируем программу:
bin/mutex.exe: src/mutex.c

g++ -Wno-deprecated src/mutex.c -o bin/mutex.exe -D _REENTERANT -I/usr/include/nptl -I/usr/lib/nptl -lpthread
Затем запустим mutex.exe на выполнение:



2. Синхронизация с помощью семафоров.
Описанную выше программу можно реализовать с использованием для синхронизации потоков семафора. При этом исходный текст программы изменяется незначительно:
#include


#include

#include

#include
ofstream output;

ifstream input;
sem_t semaphore;
const char* filename = "file.dat";

int n = 1;
void* Nums( void* arg ); // deals with numbers that factorial is then made of

void* Fact( void* arg ); // count the sum and print out the factorial of n
int main() {

cout

cin >> n;



sem_init( &semaphore, 0, 1 );

pthread_t NumsThread, FactThread;

pthread_create( &NumsThread, NULL, Nums, NULL );

pthread_create( &FactThread, NULL, Fact, NULL );
pthread_join( NumsThread, NULL );

pthread_join( FactThread, NULL );
sem_destroy( &semaphore );

}
void* Nums( void* arg ) {

for( int i = 1; i

sem_wait( &semaphore );

output.open( filename );

output

output.close();

sem_post( &semaphore );

sleep( 1 );

}

}
void* Fact( void* arg ) {

int num, fact = 1;

cout

for( int i = 1; i

sem_wait( &semaphore );

input.open( filename );

input >> num;

cout

fact *= num;

input.close();

sem_post( &semaphore );

sleep( 1 );

}

cout

}
С помощью простейшего makefile скомпилируем программу:
bin/semaph.exe: src/semaph.c

g++ -Wno-deprecated src/semaph.c -o bin/semaph.exe -D _REENTERANT -I/usr/include/nptl -I/usr/lib/nptl -lpthread
Затем запустим semaph.exe на выполнение:

Вывод данной программы аналогичен выводу программы, приведенной в предыдущем пункте. Таким образом, способ использования семафора сходен со способом использования мьютекса.
3. Синхронизация с помощью условной переменной.
В данной программе присутствуют два потока, обращающиеся по очереди к общей переменной. В первом потоке происходит сложение разделяемой переменной со случайными числами. Во втором потоке происходит вычитание из получившегося числа тройки. Каждый поток при выходе посылает сигнал другому (pthread_cond_signal( &pcond )), а сам ожидает (pthread_cond_wait( &pcond, &pmutex) ).
#include


#include

#include

#include
void* Add(void* arg);

void* Sub(void* arg);
int num = 21;

int add = 0;
pthread_mutex_t pmutex = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t pcond = PTHREAD_COND_INITIALIZER;
int main() {

srand( time( NULL ) );
pthread_mutex_init( &pmutex, 0 );

pthread_cond_init( &pcond, 0 );
pthread_t AddT, SubT;

pthread_create( &AddT, NULL, Add, NULL );

pthread_create( &SubT, NULL, Sub, NULL );
pthread_join( AddT, NULL );

pthread_cancel( SubT );

pthread_mutex_destroy( &pmutex );

pthread_cond_destroy( &pcond );

}
void* Add( void* arg ) {

for ( int i = 0; i

pthread_mutex_lock( &pmutex );

add = rand() % 10 + 1;

cout

num += add;

cout

pthread_cond_signal( &pcond );

pthread_cond_wait( &pcond, &pmutex );

pthread_mutex_unlock( &pmutex );

}

}

void* Sub( void* arg ) {

pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, NULL );

while ( true ) {

pthread_mutex_lock( &pmutex );

cout

num -= 3;

cout

pthread_cond_signal( &pcond );

pthread_cond_wait( &pcond, &pmutex );

pthread_mutex_unlock( &pmutex );

}

}

Вывод: Использование потоков может быть хорошим инструментом при необходимости обеспечить одновременное выполнение нескольких действий, в частности позволяет избежать простоев при вводе данных, однако усложняет программу и вызывает во многих случаях необходимость синхронизации при обращении к общим данным.


База данных защищена авторским правом ©infoeto.ru 2022
обратиться к администрации
Как написать курсовую работу | Как написать хороший реферат
    Главная страница