Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
5
Добавлен:
26.02.2022
Размер:
220.26 Кб
Скачать

ЛР 4 СПО Вариант 2

Задание

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

1. Создать программу из пяти потоков — основного, А, B, C и D:

1.1.Основной поток запускает остальные четыре и ожидает их завершения функцией

WaitForMultipleObjects().

1.2.Поток A добавляет в список S числа 1, 2, 3 и т. д.

1.3.Поток B извлекает из списка S последний элемент, возводит его в квадрат и помещает в список R. Если в списке S нет элементов, поток B ожидает одну секунду функцией Sleep().

1.4.Поток C извлекает из списка S последний элемент, делит его на 3 и помещает в список R. Если в списке S нет элементов, поток C ожидает одну секунду.

1.5.Поток D извлекает из списка R последний элемент и печатает его. Если в списке R нет элементов, поток D печатает сообщение об этом и ожидает одну секунду. Синхронизацию потоков производить на данном этапе не нужно.

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

2. Обеспечить корректную синхронизацию потоков.

2.1.Каждое обращение к спискам S и R из любого потока защитить критической областью (одна КО для списка S, другая — для списка R).

2.2.Запустить программу несколько раз, пронаблюдать результаты, занести в отчет: 1) результаты наблюдений;

2) граф использования потоками A, B, C и D ресурсов R и S.

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

3. Спровоцировать взаимоблокировку вследствие состязания.

3.1.Изменить алгоритм потока B на следующий: 1) вход в КО для списка S, вход в КО для списка R;

2) извлечение элемента из S, вычисление, добавление элемента в R; 3) выход из КО для списка R, выход из КО для списка S.

3.2.Аналогично изменить и алгоритм потока C, но порядок входа и выхода из критической области сделать другим.

3.3.Повторить пункт 2.2.

Указание. В целях наглядности можно между обращениями к КО в обоих потоках вставить задержки функцией Sleep(). Так можно проверить сценарии с разным порядком входа и выхода из КО. Примечание. Ожидается «зависание» потоков A, B и С, что будет проявляться в постоянно пустом списке R.

4. Спровоцировать блокировку при неконтролируемом удержании КО.

4.1.В версии программы, полученной в п. 2, внести изменение в алгоритм потока B: после захвата КО для списка R с вероятностью 0,1 следует произвести выход из потока функцией ExitThread().

4.2.Запустить программу, занести в отчет и объяснить результаты.

Пункт 1

#include <cstdio> #include <process.h> #include <windows.h>

// Структура узла списка struct Node

{

explicit Node(unsigned x) : value(x)

{}

1

Node* next = nullptr;

// следующий узел

unsigned value = 0;

// хранимое значение

};

// Класс списка (очереди на односвязном списке) class Queue

{

public:

Queue() = default; Queue(const Queue&) = delete;

Queue& operator=(const Queue&) = delete;

~Queue()

{// удаление всех узлов очереди while (head) // пока есть узлы

{

auto temp = head;

// получаем первый узел

head = temp->next;

// сдвигаем начало очереди на следующий узел

delete temp;

// удаляем узел

}

}

// Вставка значения в очередь void Push(unsigned x)

{

auto node = new Node(x); // создаем новый узел

if (tail)

// если очередь непуста

tail->next = node; // добавляем в конец

else

// если очередь пуста

head = node;

// новый узел будет первым в очереди

tail = node;

// устанавливаем новый конец

}

//Извлечение значения из очереди.

//Если очередь непуста, в out_val записывается

//извлекаемое значение и возвращается true.

//Иначе, возвращается false, а out_val не изменяется. bool Pop(unsigned* out_val)

{

if (!head)

// очередь пуста

return false;

 

 

*out_val = head->value; // пишем извлекаемое значение

Node* tmp = head;

// этот узел удалим ниже

head = tmp->next;

// передвигаем начало очереди на следующий узел

if (!head)

// если это был последний узел

tail = nullptr;

// делаем конец пустым

delete tmp;

 

// удаляем узел с извлеченным значением

return true;

 

 

}

private:

Node* head = nullptr; // первый узел

2

Node* tail = nullptr; // последний узел

};

//Очереди, общие для всех потоков

Queue S, R;

//Функция потока А

unsigned __stdcall ThreadProcA(void*)

{

unsigned x = 0;

// в цикле добавляем в S значения 1, 2, 3 и т.д. while (true)

S.Push(++x); return 0;

}

// Функция потока B

unsigned __stdcall ThreadProcB(void*)

{

while (true)

 

 

{

 

 

unsigned x;

 

 

if (S.Pop(&x))

// если извлекли значение из S

R.Push(x * x);

// добавляем его квадрат в R

else

// иначе, ждем секунду

Sleep(1000);

}

return 0;

}

// Функция потока C

unsigned __stdcall ThreadProcC(void*)

{

while (true)

 

 

{

 

 

unsigned x;

 

 

if (S.Pop(&x))

// если извлекли значение из S

R.Push(x / 3);

// добавляем в R значение/3

else

// иначе, ждем секунду

Sleep(1000);

}

return 0;

}

// Функция потока D

unsigned __stdcall ThreadProcD(void*)

{

while (true)

 

{

 

unsigned x;

 

if (R.Pop(&x))

// если извлекли значение из R

printf("%u\n", x); // печатаем его

else

 

{

// иначе

puts("R is empty"); // сообщаем, что R - пуста

3

Sleep(1000);

// ждем секунду

}

 

}

 

return 0;

 

}

int main()

{

const size_t threads_count = 4; HANDLE threads[threads_count];

// создание потоков

threads[0] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcA, nullptr, 0, nullptr); threads[1] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcB, nullptr, 0, nullptr); threads[2] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcC, nullptr, 0, nullptr); threads[3] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcD, nullptr, 0, nullptr);

// ожидание всех потоков

WaitForMultipleObjects(threads_count, threads, TRUE, INFINITE); for (auto h : threads)

CloseHandle(h); return 0;

}

Пункт 2

#include <cstdio> #include <process.h> #include <windows.h>

// Структура узла списка struct Node

{

explicit Node(unsigned x) : value(x)

{}

 

Node* next = nullptr;

// следующий узел

unsigned value = 0;

// хранимое значение

};

// Класс списка (очереди на односвязном списке) class Queue

{

public:

Queue() = default; Queue(const Queue&) = delete;

Queue& operator=(const Queue&) = delete;

~Queue()

{// удаление всех узлов очереди while (head) // пока есть узлы

{

auto temp = head;

// получаем первый узел

head = temp->next;

// сдвигаем начало очереди на следующий узел

4

delete temp;

// удаляем узел

}

 

}

 

// Вставка значения в очередь void Push(unsigned x)

{

auto node = new Node(x); // создаем новый узел

if (tail)

// если очередь непуста

tail->next = node; // добавляем в конец

else

// если очередь пуста

head = node;

// новый узел будет первым в очереди

tail = node;

// устанавливаем новый конец

}

//Извлечение значения из очереди.

//Если очередь непуста, в out_val записывается

//извлекаемое значение и возвращается true.

//Иначе, возвращается false, а out_val не изменяется. bool Pop(unsigned* out_val)

{

if (!head)

// очередь пуста

return false;

 

 

*out_val = head->value; // пишем извлекаемое значение

Node* tmp = head;

// этот узел удалим ниже

head = tmp->next;

// передвигаем начало очереди на следующий узел

if (!head)

// если это был последний узел

tail = nullptr;

// делаем конец пустым

delete tmp;

 

// удаляем узел с извлеченным значением

return true;

 

 

}

private:

Node* head = nullptr; // первый узел Node* tail = nullptr; // последний узел

};

//Очереди, общие для всех потоков

Queue S, R;

//КО для общих очередей

CRITICAL_SECTION csec_s, csec_r;

//Функция потока А

unsigned __stdcall ThreadProcA(void*)

{

unsigned x = 0;

// в цикле добавляем в S значения 1, 2, 3 и т.д. while (true)

{

EnterCriticalSection(&csec_s); // Вход в КО для S S.Push(++x); // добавляем очередное значение в S LeaveCriticalSection(&csec_s); // Выход из КО для S

5

//Если производитель заносит значение в список S быстрее,

//чем потребители извлекают эти значения, обрабатывают

//и помещают в другую очередь R, очередь S может расти быстрее

//потребления, постоянно разрастаясь и потребляя память.

//Задержка ниже присутствует для гарантии, что все значения из S

//будут извлечены и очередь S не будет постоянно разрастаться.

//Периодичность задержки (каждые 4000 значений) и время

//задержки (2 секунды) можно менять в зависимости от машины. if (x % 4000 == 0)

Sleep(2000);

}

return 0;

}

// Функция потока B

unsigned __stdcall ThreadProcB(void*)

{

while (true)

{

unsigned x;

 

EnterCriticalSection(&csec_s);

// Вход в КО для S

auto extracted = S.Pop(&x);

// пытаемся извлечь значение из S

LeaveCriticalSection(&csec_s); // Выход из КО для S

if (extracted)

{// извлекли значение из S EnterCriticalSection(&csec_r); // Вход в КО для R

R.Push(x * x); // добавляем его квадрат в R LeaveCriticalSection(&csec_r); // Выход из КО для R

}

else // иначе, ждем секунду

Sleep(1000);

}

return 0;

}

// Функция потока C

unsigned __stdcall ThreadProcC(void*)

{

while (true)

{

unsigned x;

EnterCriticalSection(&csec_s); // Вход в КО для S

auto extracted = S.Pop(&x); // пытаемся извлечь значение из S LeaveCriticalSection(&csec_s); // Выход из КО для S

if (extracted)

 

 

{ // извлекли значение из S

 

EnterCriticalSection(&csec_r);

// Вход в КО для R

R.Push(x / 3);

// добавляем в R значение/3

LeaveCriticalSection(&csec_r);

// Выход из КО для R

}

 

 

else // иначе, ждем секунду

 

6

// иначе
puts("R is empty"); // сообщаем, что R - пуста Sleep(1000); // ждем секунду

Sleep(1000);

}

return 0;

}

// Функция потока D

unsigned __stdcall ThreadProcD(void*)

{

while (true)

{

unsigned x;

 

 

EnterCriticalSection(&csec_r);

// Вход в КО для R

auto extracted = R.Pop(&x);

// пытаемся извлечь значение из R

LeaveCriticalSection(&csec_r); // Выход из КО для R

if (extracted)

// если извлекли значение из R

printf("%u\n", x); // печатаем его else

{

}

}

return 0;

}

int main()

{

const size_t threads_count = 4; HANDLE threads[threads_count];

//Инициализация КО

InitializeCriticalSection(&csec_s); InitializeCriticalSection(&csec_r);

//создание потоков

threads[0] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcA, nullptr, 0, nullptr); threads[1] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcB, nullptr, 0, nullptr); threads[2] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcC, nullptr, 0, nullptr); threads[3] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcD, nullptr, 0, nullptr);

// ожидание всех потоков

WaitForMultipleObjects(threads_count, threads, TRUE, INFINITE); for (auto h : threads)

CloseHandle(h);

// Удаление КО

DeleteCriticalSection(&csec_r); DeleteCriticalSection(&csec_s); return 0;

}

Пункт 3

7

#include <cstdio> #include <process.h> #include <windows.h>

// Структура узла списка struct Node

{

explicit Node(unsigned x) : value(x)

{}

 

Node* next = nullptr;

// следующий узел

unsigned value = 0;

// хранимое значение

};

// Класс списка (очереди на односвязном списке) class Queue

{

public:

Queue() = default; Queue(const Queue&) = delete;

Queue& operator=(const Queue&) = delete;

~Queue()

{ // удаление всех узлов очереди while (head) // пока есть узлы

{

auto temp = head;

// получаем первый узел

head = temp->next;

// сдвигаем начало очереди на следующий узел

delete temp;

// удаляем узел

}

}

// Вставка значения в очередь void Push(unsigned x)

{

auto node = new Node(x); // создаем новый узел

if (tail)

// если очередь непуста

tail->next = node; // добавляем в конец

else

// если очередь пуста

head = node;

// новый узел будет первым в очереди

tail = node;

// устанавливаем новый конец

}

//Извлечение значения из очереди.

//Если очередь непуста, в out_val записывается

//извлекаемое значение и возвращается true.

//Иначе, возвращается false, а out_val не изменяется. bool Pop(unsigned* out_val)

{

if (!head)

// очередь пуста

return false;

 

*out_val = head->value; // пишем извлекаемое значение Node* tmp = head; // этот узел удалим ниже

8

head = tmp->next;

// передвигаем начало очереди на следующий узел

if (!head)

// если это был последний узел

tail = nullptr;

 

// делаем конец пустым

delete tmp;

 

// удаляем узел с извлеченным значением

return true;

 

 

}

private:

Node* head = nullptr; // первый узел Node* tail = nullptr; // последний узел

};

//Очереди, общие для всех потоков

Queue S, R;

//КО для общих очередей

CRITICAL_SECTION csec_s, csec_r;

//Функция потока А

unsigned __stdcall ThreadProcA(void*)

{

unsigned x = 0;

// в цикле добавляем в S значения 1, 2, 3 и т.д. while (true)

{

EnterCriticalSection(&csec_s); // Вход в КО для S S.Push(++x); // добавляем очередное значение в S LeaveCriticalSection(&csec_s); // Выход из КО для S

}

return 0;

}

// Функция потока B

unsigned __stdcall ThreadProcB(void*)

{

while (true)

{

unsigned x;

EnterCriticalSection(&csec_s); // Вход в КО для S

EnterCriticalSection(&csec_r); // Вход в КО для R

auto extracted = S.Pop(&x); // пытаемся извлечь значение из S

if (extracted)

// если извлекли значение из S

R.Push(x * x);

// добавляем его квадрат в R

LeaveCriticalSection(&csec_r);

// Выход из КО для R

LeaveCriticalSection(&csec_s);

// Выход из КО для S

if (!extracted) // если не извлекли

Sleep(1000);

// ждем секунду

}

return 0;

}

9

// Функция потока C

unsigned __stdcall ThreadProcC(void*)

{

while (true)

{

unsigned x;

 

 

EnterCriticalSection(&csec_r);

// Вход в КО для R

EnterCriticalSection(&csec_s);

// Вход в КО для S

auto extracted = S.Pop(&x); // пытаемся извлечь значение из S

if (extracted)

// если извлекли значение из S

R.Push(x / 3);

// добавляем в R значение/3

LeaveCriticalSection(&csec_s);

// Выход из КО для S

LeaveCriticalSection(&csec_r);

// Выход из КО для R

if (!extracted)

// если не извлекли

Sleep(1000);

// ждем секунду

}

 

 

return 0;

 

 

}

 

 

// Функция потока D

unsigned __stdcall ThreadProcD(void*)

{

while (true)

 

{

 

unsigned x;

 

if (R.Pop(&x))

// если извлекли значение из R

printf("%u\n", x); // печатаем его

else

 

{

// иначе

puts("R is empty"); // сообщаем, что R - пуста

Sleep(1000);

// ждем секунду

}

 

}

 

return 0;

 

}

 

int main()

{

const size_t threads_count = 4; HANDLE threads[threads_count];

// Инициализация КО

InitializeCriticalSection(&csec_s); InitializeCriticalSection(&csec_r);

// создание потоков

threads[0] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcA, nullptr, 0, nullptr); threads[1] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcB, nullptr, 0, nullptr); threads[2] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcC, nullptr, 0, nullptr); threads[3] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProcD, nullptr, 0, nullptr);

10

Соседние файлы в папке Windows лаб 1-4