Лабораторная работа по курсу "Операционные системы" Лабораторная работа по курсу "Операционные системы"
Потоки в ОС Linux
Цель работы: знакомство с системными вызовами для управления потоками в ОС Linux.
I. Управление потоками
Основная цель использования потоков - это разделение программы на подзадачи, которые могут выполняться параллельно. По сравнению с процессами взаимодействие и синхронизация потоков требует меньше времени, поскольку потоки одного процесса выполняются в одном адресном пространстве.
В ОС UNIX/Linux имеется API для потоков стандарта POSIX (Portable Operating System Interface) - pthreads ("P" - от POSIX). Прототипы функций работы с потоками и необходимые типы данных содержатся в заголовочном файле <pthread.h>. Эти функции не включены в стандартную библиотеку языка С, они находятся в библиотеке libthread. Поэтому в командную строку для компоновки необходимо добавить опцию (см. лабораторную работу 2)
-lpthread
Каждый поток имеет свой идентификатор потока, ID потока. В программах на С/С++ для ID потоков следует использовать тип pthread_t из <sys/types/.h>.
Создание и завершение потоков
Создание потока. Поток создается функцией pthtead_create, имеющей 4 параметра:
Указатель на переменную типа pthread_t, в нее будет записан ID нового потока.
Указатель на объект атрибут потока. Этот объект управляет деталями взаимодействия потока с остальной программой. Если параметр равен NULL, то поток будет создан с атрибутами по умолчанию.
Указатель на функцию потока. Это обычный указатель на функцию типа void*(*)(void*), т.е. функция потока принимает один параметр типа указатель на void и возвращает значение типа указатель на void..
Значение атрибута потока типа void*. Это значение передается потоку как аргумент в функцию потока. Через него можно передать новому потоку параметры.
После создания каждый поток выполняет функцию потока - обычную функцию в программе пользователя. При завершении этой функции поток завершается.
Возврат из функции pthread_create происходит немедленно, и исходный поток продолжает выполнение команд, следующих за вызовом pthread_create. Одновременно новый поток начинает выполнение функции потока. Функция pthread_create возвращает ноль в случае успеха или не ноль в случае ошибки.
Завершение потока. При нормальных условиях поток завершается двумя способами:
Обычный возврат из функции потока. Величина, возвращаемая return, будет являться значением, возвращаемым потоком.
Возврат при помощи функции pthread_exit. Она может быть вызвана из любой функции данного потока. Аргумент этой функции будет являться значением, возвращаемым потоком.
Задание 1. Выполните программу pr1.c. Программа создает поток, который непрерывно печатает 'x' на устройстве stderr. После создания потока главный поток непрерывно печатает 'o' на stderr. (Приостановить выполнение программы можно при помощи Ctrl-s; возобновить - любой клавишей. Снять программу можно при помощи Ctrl-c.)
/* pr1.c */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* print_xs (void* unused)
{
while (1)
fputc (‘x’, stderr);
return NULL;
}
int main ()
{
int p;
pthread_t thread_id;
p = pthread_create (&thread_id, NULL, &print_xs, NULL);
if (p != 0) { perror("Thread problem"); exit(1);}
while (1)
fputc (‘o’, stderr);
return 0;
}
Что произойдет, если stderr заменить на stdout? Добавьте в программу печать идентификаторов обоих потоков, убрав операторы цикла while. Воспользуйтесь функцией pthread_self (Возвращает ID потока, в котором была вызвана. Не имеет параметров.).
Передача данных в поток
Т.к. тип аргумента, передаваемого в поток, - void*, то для передачи одного параметра типа int его следует преобразовать к типу (void*) (См., например, текст программы simple_threads.c в приложении.) Для передачи большего количества параметров аргумент потока должен быть указателем на структуру или область данных, содержащую передаваемые параметры.
Необходимо, чтобы данные, передаваемые новому потоку, были доступны потоку, при этом не следует передавать стековые переменные. Как Вы думаете, почему?
Задание 2. Выполните программу pr2.c. Программа создает два новых потока: один печатает 'x', другой 'o' на устройстве stderr. Каждый поток печатает определенное количество символов и затем завершается возвратом из функции потока. Оба потока используют одну и ту же функцию, char_print, но вызывают ее с разными значениями параметров.
/* pr2.c */
#include <pthread.h>
#include <stdio.h>
struct char_print_parms
{
char character; /* Символ, который печатать */
int count; /* Сколько раз печатать символ */
};
void* char_print (void* parameters)
{
/* Преобразовать указатель к нужному типу */
struct char_print_parms* p = (struct char_print_parms*) parameters;
int i;
for (i = 0; i < p->count; ++i)
fputc (p->character, stderr);
return NULL;
}
int main ()
{
pthread_t thread1_id;
pthread_t thread2_id;
struct char_print_parms thread1_args;
struct char_print_parms thread2_args;
thread1_args.character = ’x’;
thread1_args.count = 30; /* Печатать 'x' 30 раз */
pthread_create (&thread1_id, NULL, &char_print, &thread1_args);
thread2_args.character = ’o’;
thread2_args.count = 20; /* Печатать 'о' 20 раз */
pthread_create (&thread2_id, NULL, &char_print, &thread2_args);
return 0;
}
Внимание! Данная программа содержит ошибку. Для отладки программы добавьте в функцию потока печать переданных параметров. Исправьте ошибку и поясните ее причину.
Объединение потоков
В случае потоков аналогом функции wait является функция pthread_join: поток, вызвавший эту функцию, будет ожидать завершения указанного потока. Функция возвращает ноль в случае нормального выполнения, и не ноль в случае ошибки. Функция имеет два параметра: ID потока, завершения которого следует ожидать, и переменную типа указатель на void, куда будет записано значение, возвращаемое потоком. Если это значение не требуется, то второй параметр функции pthread_join может быть NULL.
Задание 3. Создайте программу pr3.c, модифицировав программу pr2.c: a) добавьте в главный поток вызов функций pthread_join для ожидания завершения обоих дочерних потоков; b) верните из дочерних потоков какие-нибудь значения (разные!) и распечатайте их в главном потоке.
II. Сравнение потоков и процессов
Изучите работу четырех программ, makefile, simple.c, simple_processes.c, simple_threads.c, simple_mutex.c, приведенных в Приложении (Тексты программ и makefile содержатся в файле appendix.doc):
Обычная программа simple.c, состоящая из функции main и двух дополнительных функций; не использует многозадачности или многопоточности.
Программа simple_processes.c запускает три процесса - родительский и два дочерних.
Программа simple_threads.c запускает три потока - главный (первичный) и два дочерних.
Программа simple_mutex.c с тремя потоками, как в (с), и использующая мьютекс для синхронизации.
Программы (a), (b) и (c) выполняют одинаковые вычисления.
Задание 4.
4.1. Скопируйте файлы makefile, simple.c, simple_processes.c, simple_threads.c, simple_mutex.c в ваш каталог. Для создания исполняемых файлов всех четырех программ запустите утилиту make без параметров.
4.2. Запустите исполняемые файлы simple, simple_processes и simple_threads. Объясните полученные результаты.
4.3. Попытайтесь оценить время выполнения программ simple, simple_processes и simple_threads. Это можно сделать одним из двух способов:
Использовать в функции main всех трех программ функцию gettimeofday (не забудьте включить <sys/time.h>). Эту функцию следует включить в два места программы: до и после вызова двух функций в simple; до и после создания и запуска двух процессов/потоков в simple_processes/simple_threads. Для получения более точного результата исключите printf из функций do_one_thing и do_another_thing.
Использовать обычные часы. Для того чтобы программа выполнялась несколько минут, следует включить в main каждой программы цикл, повторяющийся не менее 32000 раз. Для получения более точного результата исключите printf из функций do_one_thing и do_another_thing.
Объясните разницу во времени работы трех программ.
4.4. Выполните программу simple_mutex. Проверьте, что переменная common (изменяемая параллельно двумя потоками), изменяет свою величину от 0 до 100 (т.к. каждый поток изменяет эту переменную ровно 50 раз). Обратите внимание, что в каждый момент времени переменная common читается, увеличивается на 1 и затем записывается без прерывания только одним потоком. Это обеспечивается механизмом мьютексов, который используется обоими потоками (см. функции pthread_mutex_lock и pthread_mutex_unlock).
4.5. Затем удалите механизм мьютексов из программы simple_mutex_c, перекомпилируйте ее и выполните несколько раз. Одинаковы ли значения common в разных запусках? Всегда ли переменная common имеет конечное значение, равное 100? Объясните результаты.
4.6. Между двумя последовательными чтением и записью common, каждый из двух потоков в программе simple_mutex.c выполняет длинный цикл. Каково влияние продолжительности этого цикла на переменную common? Для ответа на этот вопрос выполните еще три эксперимента без механизма мьютексов. В первом эксперименте удалите цикл полностью. Во втором эксперименте уменьшите продолжительность цикла в два раза по сравнению с начальной версией программы. В третьем эксперименте увеличьте продолжительность цикла в два раза по сравнению с начальной версией программы. Запишите и объясните результаты экспериментов.
Порядок выполнения лабораторной работы
Выполните задания 1-4.
Занесите в отчет описание заданий со всеми требуемыми пояснениями и ответами на вопросы.
Требования
При подготовке к лабораторной работе (дома) занесите в отчет тексты программ из заданий 1-3. Тексты программ должны быть прокомментированы.
Студент должен знать ответы на следующие вопросы:
Вопросы
Что такое поток? Какова цель использования потоков в программах?
Какова разница между потоками и процессами?
Когда потоки могут выполняться действительно параллельно?
Как можно создать в программе новый поток?
Как передать в поток параметры и получить из потока возвращаемое значение?
Может ли функция, выполняемая потоком, быть описана вне программы?
Могут ли два потока использовать одну и ту же функцию?
Что произойдет с потоком, если поток, создавший его, завершится?
Возможно ли выполнить многопоточную программу на многопроцессорной системе и на нескольких компьютерах, объединенных в сеть?
Что такое критическая секция в многопоточной программе? Найдите критическую секцию в программе simple_mutex.c.
Источники информации
Митчел М., Оулдем Дж., Самьюэл А. Программирование для Linux. Профессиональный подход. - М.: Издательский дом "Вильямс", 2003. (Глава 4) (The original book (2001) is available at http://www.newriders.com or http://www.advancedlinuxprogramming.com)
Ш. Уолтон. Создание сетевых приложений в среде Linux. - М.: Издательский дом "Вильямс", 2001. (Глава 7)
---------------------------------------------------------------------------------------------------
© Лабораторная работа подготовлена Л.В. Илюшечкиной и А.Е.Костиным (раздел II).
