Лабораторные работы / Windows лаб 1-4 / lab4_var2_kod
.pdfЛР 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
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