ФЕДЕРАЛЬНОЕ АГЕНТСТВО СВЯЗИ
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ
«САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ ИМ. ПРОФ. М.А. БОНЧ-БРУЕВИЧА»
(СПбГУТ)
Кафедра программной инженерии и вычислительной техники
Отчёт
по лабораторной работе №6 на тему: «Разработка многопоточного приложения под Linux»
по дисциплине «Операционные системы»
Выполнил: студент группы ИКВТ-61, UКозырев А.Б.
« » 2018 г. ___________/А.Б. Козырев/
Принял: __к.т.н. Дагаев А.В.
« » 2018 г. ___________/_А.В. Дагаев/
Задание: разработать многопоточное приложение с применением приоритетов и методов синхронизации потоков под Linux.
Объект синхронизации: семафор.
Пример использования:
Исходный код:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <cmath>
#include <semaphore.h>
#include <unistd.h>
using namespace std;
typedef struct
{
uintptr_t *a;
uintptr_t length;
uintptr_t sum;
} MyData;
#define N 5
#define L 20
static MyData mData;
static pthread_t myThread[N];
static sem_t sem1, sem2;
static uintptr_t *a = reinterpret_cast<uintptr_t*>( malloc (N*L*sizeof(uintptr_t)) );
void *threadWork(void *arg)
{
uintptr_t offset = reinterpret_cast<uintptr_t>(arg);
uintptr_t sum = 0;
uintptr_t start = offset * mData.length;
uintptr_t end = start + mData.length;
//Критическая секция №3
sem_wait(&sem1);
printf("\nThread \"%llu\" at work\n", offset + 1);
for (uintptr_t i = 0; i < L/N; i++)
{
for (uintptr_t j = start + N*i; j < start + N*i + N; j++)
{
printf(" [%3llu] ", mData.a[j]);
}
puts("");
}
for (uintptr_t i = start; i < end ; i++)
sum += mData.a[i];
printf ("\tSum = %llu \n", sum);
mData.sum += sum;
sem_post(&sem1);
//Конец критической секции №3
pthread_exit(nullptr);
}
void *createArray(void *arg)
{
uintptr_t offset = reinterpret_cast<uintptr_t>(arg);
uintptr_t start = offset * mData.length;
uintptr_t end = start + mData.length;
//Критическая секция №1
sem_wait(&sem2);
for (uintptr_t i = start; i < end ; i++)
a[i] = pow((i+1), 1);
int value;
sem_getvalue(&sem2, &value);
while(value != 0)
{
sem_getvalue(&sem2, &value);
}
sleep(1);
//Конец критической секции №1
//Критическая секция №2
sem_wait(&sem1);
sem_getvalue(&sem2, &value);
if ( value == 0 )
{
sem_post(&sem2);
puts("The array data present:");
puts("");
for (uintptr_t i = 0; i < L ; i++)
{
for (uintptr_t j = i*N; j < i*N + N; j++)
{
printf(" [%3llu] ", mData.a[j]);
}
puts("");
}
}
sem_post(&sem1);
//Конец критической секции №2
threadWork(arg);
}
int main ()
{
mData.length = L;
mData.a = a;
mData.sum = 0;
void *status;
sem_init(&sem1, 0, 1);
sem_init(&sem2, 0, N);
for(long i=0; i < N; i++)
pthread_create(&myThread[i], nullptr, createArray, reinterpret_cast<void*>(i));
for(int i=0; i < N; i++)
pthread_join(myThread[i], &status);
printf ("\n\tSum = %llu \n", mData.sum);
free (a);
sem_destroy(&sem1);
sem_destroy(&sem2);
return 0;
}
Рассмотрим работу данной процедуры с предпроцессорных и глобальных объявлений.
Создана глобальная структура MyData с тремя полями: *a, length, sum.
В этих атрибутах будут храниться данные с результатами. Для указателя *a выделяется память под элементов типа int. Затем создаются объект этой структуры с идентификатором mData, а также: N переменных потока pthread_t, два семафора: sem1 и sem2.
Рассмотрим работу программы по шагам:
Функция main():
-
Инициализирует данные структуры mData;
-
Инициализирует семафор sem1 со значением 1;
-
Инициализирует семафор sem2 со значением N (кол-во потоков);
-
Создаёт N потоков через цикл for и передаёт их в функцию createArray, с аргументами в виде счётчика для идентификации этих потоков;
-
С помощью функции pthread_join() дожидается завершения каждого из потоков;
-
Выводит результат из атрибута mData.sum на стандартный поток вывода;
-
Освобождает указатель *a;
-
Уничтожает семафоры sem1, sem2.
-
Завершается с кодом «0» в случае успеха.
Функция createArray():
-
Присваивает аргумент потока в переменную offset;
-
Инициализирует начало и конец для индексирования массива;
-
Заходит в критическую секцию №1;
-
В критической секции №1 каждый поток декрементирует семафор sem2 и начинает заполнять свой сегмент массива a;
-
Получает текущее состояние семафора sem2 с помощью функции sem_getvalue() и заходит в цикл while(), где ожидает прохождения критической секции №1 последним потоком;
-
Ожидает 1 секунду для большей синхронизации по времени;
-
-
Заходит в критическую секцию №2;
-
В критической секции №2 каждый поток блокирует этот блок кода семафором sem1, который выступает в роли мьютекса;
-
Получает текущее значение семафора sem2;
-
Проверяет значение семафора sem2 на нуль и, если это так, то он заходит в блок условия if и вызывает функцию sem_post() для инкремента семафора sem2, чтобы другие потоки не смогли зайти в этот блок;
-
Этот единственный поток выводит заполненный массив целиком по 5 элементов на строчку, затем выводит пустую строку и освобождает семафор sem1;
-
Каждый поток так или иначе заходит в критическую секцию №2, но не каждый заходит в блок с условным оператором if, затем освобождает семафор sem1 и выходит из это критической секции;
-
Далее, каждый поток вызывает функцию threadWork(), где продолжает свою работу.
Функция threadWork():
-
Присваивает аргумент потока в переменную offset;
-
Инициализирует начало и конец для индексирования массива;
-
Заходит в критическую секцию №3 с помощью семафора sem1;
-
Выводит приветствие в поток вывода;
-
Выводит свою часть массива на экран;
-
Вычисляет сумму своих элементов массива;
-
Выводит сумму своих элементов массива на экран;
-
Суммирует свою сумму из предыдущего пункта с общей суммой;
-
Выходит из критической секции №3;
-
Завершает свою работу с помощью функции pthread_exit().
-
В результате такого алгоритма заметим следующее:
Семафор sem2 выступает в ролях: барьера в критической секции №1, условной переменной в критической секции №2.
Семафор sem1 выступает в ролях: мьютекса в критических секциях №2 и №3.
Сложнее всего была реализации критической секции №2, так как требовалось обеспечить вывод массива только одним потоком, с тем условием, чтобы другие потоки успели пройти критическую секцию №1, именно для этого используется задержка sleep(1); если бы не эта задержка на 1 секунду, то другой поток мог не успеть выйти из критической секции №1, а может и несколько. Покажем пример каждого случая после корректной работы программы.
Результат работы программы:
Случай 1: задержка на 1 секунду после барьера в критической секции №1 отсутствует.
Потоки №№ 1, 2 и 5 застревают в бесконечном цикле while(), где значение value закрепилось потоками 3 и 4 в ненулевом состоянии.
Случай 2: отсутствие мьютекса перед входом в оператор if.
Никто не дожидается потока 2, другие потоки просто проскакивают мимо!
Вывод:
В результате мы достигли следующего:
-
Научились работать с потоками в системе Linux, также применили знания для написания программы;
-
Повторили материал по потокам ввода-вывода, стандартным конструкциям языка C++;
-
Использовали семафор в ролях: мьютекса, условной переменной, барьера – для синхронизации одновременно выполняющихся потоков;
САНКТ-ПЕТЕРБУРГ 2018