Вы находитесь на странице: 1из 6

Лабораторная работа №3

«Синхронизация потоков»

Цель работы: Изучение функций, предназначенных для синхронизации потоков в ОС


Linux.

Задачи: Получение практических навыков использования мьютексов и условных


переменных для решения задач доступа к ресурсам.

Задания на лабораторную работу:


1. Выполните следующие действия. Для каждого изменения опишите, что происходит, и дайте
объяснение – почему.
1) Уберите в первом примере функцию sleep.
2) Уберите во втором примере функцию освобождения мьютекса из порожденного потока.
3) Уберите в третьем примере функцию pthread_cond_signal.

2. Решите классическую проблему «поставщик – потребитель» с использованием описанных в


лабораторной работе средств синхронизации.

Постановка задачи:
Один поток производит данные, другой поток их потребляет. В промежуток времени между
изготовлением и потреблением данные хранятся в буфере.
Пример использования: Конвейер команд в Unix.
Исходные данные:
Данные хранятся в циклическом буфере. Циклический буфер описывается некоторой
областью памяти, указателем начала данных и указателем конца данных. Поток-поставщик
записывает данные в конец буфера, поток-потребитель считывает их с начала буфера. После записи
или чтения соответствующим образом меняются указатели начала и конца.
Операции чтения/записи должны быть выполнены как взаимоисключающие.
Если операция чтения выполняется над пустым буфером (указатель начала = указатель
конца), поток-потребитель должен быть заблокирован на условной переменной до тех пор, пока
поток-поставщик не запишет в буфер какие-нибудь данные. Если операция записи выполняется над
полным буфером, поток-поставщик должен также быть заблокирован на условной переменной до тех
пор, пока поток-потребитель не считает из буфера какие-нибудь данные.
Размер буфера – не менее 10 символов.
Поток-поставщик и поток-потребитель работают в бесконечном цикле.
Поток-поставщик производит по одному символу в последовательности 0,1,2...9,0,1,... и
записывает его в буфер через случайный интервал времени 0,5 – 2 сек.
Поток-потребитель считывает по одному символу через случайный интервал времени 0,5 – 2
сек из буфера и выводит их на экран в виде сообщений (например, Символ 0, Символ 1,...)
Каждый поток совершая операцию с буфером выводит на экран информацию о текущем
состоянии буфера до и после операции, тип операции, символ, состояние условной переменной.

Задание 1.1

После исключения функции sleep из:


1) функции потока-потомка.

Лист
Лабораторная работа №3 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 1
Сначала выполняется поток-потомок, затем поток-родитель.
Т.к. поток-потомок успевает выполниться быстрее без задержки.
2) функции потока-родителя

Сначала выполняется поток-родитель, затем поток-потомок.


Т.к. поток-родитель успевает выполнить операцию инкрементирования быстрее чем
начинает выполнение своей функции поток-потомок.
3) функции потока-родителя и потока-потомка

Сначала выполняется операция инкрементирования в функции потока-родителя, затем


происходит выполнение функции потока-потомка.
Т.к. поток-родитель быстрее успевает продолжить свое выполнение, чем поток-потомок
начать свое выполнение.

Задание 1.2
После исключения pthread_mutex_unlock(&my_sync) из порождённого потока программа
не может завершиться, т.к. порожденный поток не освободил мьютекс и участок кода в основном
потоке, заключенный в него, оказался недоступен.

Задание 1.3
После исключения pthread_cond_signal(&rx) программа не может завершиться, т.к.
основной поток остается заблокирован переменой состояния rx.

Лист
Лабораторная работа №3 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 2
Задание 2
Листинг

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <string.h>
#include <cstdlib>
#include <math.h>
#include <sys/errno.h>
#include <queue>

using namespace std;

queue<string> ThreadOutputToPrint;
bool IsPushing = false;
bool IsPopping = false;
pthread_mutex_t PushPop_Mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t IsPushingPopping_Condition = PTHREAD_COND_INITIALIZER;

float ProviderSleepInterval = 0;
float ConsumerSleepInterval = 0;

template <typename BufferValueType>


struct CircularBuffer
{
private:
BufferValueType *Buffer = nullptr;
int Length;
int ElementCount;
int LastReadIndex = -1;
int LastWriteIndex = -1;

pthread_mutex_t BufferIsBusy = PTHREAD_MUTEX_INITIALIZER;


pthread_cond_t IsFullCondition = PTHREAD_COND_INITIALIZER;

public:
CircularBuffer(int BufferLength)
{
Init(BufferLength);
};
void Init(int NewbufferLength)
{
if (Buffer != nullptr)
{
delete Buffer;
}
ElementCount = 0;
LastReadIndex = 0;
LastWriteIndex = 0;
Buffer = new BufferValueType[NewbufferLength];
Лист
Лабораторная работа №3 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 3
Length = NewbufferLength;
};
void Read_ThreadSafe(BufferValueType& Element)
{
pthread_mutex_lock(&BufferIsBusy);
while (ElementCount == 0)
{
pthread_cond_wait(&IsFullCondition, &BufferIsBusy);
}

Element = Buffer[LastReadIndex];
ElementCount--;
LastReadIndex = (LastReadIndex + 1) % Length;

string StringToPrint("Read ");


StringToPrint += Element;
StringToPrint += " Last Sleep Time = ";
StringToPrint += to_string(ConsumerSleepInterval);
cout << StringToPrint << endl;

pthread_cond_signal(&IsFullCondition);
pthread_mutex_unlock(&BufferIsBusy);
};

void Write_ThreadSafe(BufferValueType NewElement)


{
pthread_mutex_lock(&BufferIsBusy);
while (ElementCount == Length)
{
pthread_cond_wait(&IsFullCondition, &BufferIsBusy);
}

Buffer[LastWriteIndex] = NewElement;
ElementCount++;
LastWriteIndex = (LastWriteIndex + 1) % Length;

string StringToPrint("Write ");


StringToPrint += NewElement;
StringToPrint += " Last Sleep Time = ";
StringToPrint += to_string(ProviderSleepInterval);
cout << StringToPrint << endl;

pthread_cond_signal(&IsFullCondition);
pthread_mutex_unlock(&BufferIsBusy);
};
};

void *ProviderStartingPoint(void *BufferPointer)


{
CircularBuffer<char> *Buffer = (CircularBuffer<char> *)(BufferPointer);
char CurrentSymbolToWrite;
srand(pthread_self());
char AvailableSymbols[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
Лист
Лабораторная работа №3 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 4
int Counter = 0;
while (true)
{
ProviderSleepInterval = (float)((float)(rand() % 150 + 50) / (float)100);
sleep(ProviderSleepInterval);
CurrentSymbolToWrite = AvailableSymbols[Counter % 10];
Buffer->Write_ThreadSafe(CurrentSymbolToWrite);
Counter++;
}
}

void *ConsumerStartingPoint(void *BufferPointer)


{
CircularBuffer<char> *Buffer = (CircularBuffer<char> *)(BufferPointer);
srand(pthread_self());
while (true)
{
ConsumerSleepInterval = (float)((float)(rand() % 150 + 50) / (float)100);
sleep(ConsumerSleepInterval);
char ReadCharacter;
Buffer->Read_ThreadSafe(ReadCharacter);
}
}

int main(int argc, char *argv[])


{
CircularBuffer<char> Buffer(10);
pthread_t Provider_ThreadID;
pthread_t Consumer_ThreadID;
int ThreadCreationStatus = pthread_create(&Provider_ThreadID, NULL, ProviderStartingPoint,
&Buffer);
if (ThreadCreationStatus != 0)
{
cout << "Failed To Create Provider Thread: " << strerror(errno);
sleep(3);
exit(0);
}
ThreadCreationStatus = pthread_create(&Consumer_ThreadID, NULL, ConsumerStartingPoint,
&Buffer);
if (ThreadCreationStatus != 0)
{
cout << "Failed To Create Consumer Thread: " << strerror(errno);
sleep(3);
exit(0);
}
//Waiting for threads to finish work (it will never happen)
int ThreadEndStatus = pthread_join(Provider_ThreadID, NULL);
if (ThreadEndStatus != 0)
{
cout << "Provider Thread Exited With Failure: " << strerror(errno) << endl;
}
ThreadEndStatus = pthread_join(Consumer_ThreadID, NULL);
if (ThreadEndStatus != 0)
Лист
Лабораторная работа №3 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 5
{
cout << "Consumer Thread Exited With Failure: " << strerror(errno) << endl;
}
}
Результат работы

Лист
Лабораторная работа №3 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 6