Файл: Методические указания к лабораторной работе 1 по дисциплине Дисциплина по выбору 1 для студентов специальностей 200700 Радиотехника.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 23.11.2023
Просмотров: 25
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Министерство образования Российской Федерации
НИЖЕГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ
УНИВЕРСИТЕТ
Кафедра «Информационные радиосистемы»
Реализация многопоточных приложений на С++
Методические указания к лабораторной работе № 1
по дисциплине «Дисциплина по выбору 1»
для студентов специальностей 200700 «Радиотехника»
Нижний Новгород 2006
Номера страниц
УДК 621.325.5-181.4
Реализация многопоточных приложений на С++: Метод. указания к лаб. работе № 1
по дисциплине «Сетевые информационные технологии» для студентов спец. 200700
/ НГТУ; Сост.: А.В.Миронов Н. Новгород, 2006 ??с.
Изложены начальные сведения о реализации многопоточных приложений с использованием объектно-ориентированного программирования. Сформулированы задания и порядок выполнения для лабораторной работы.
Редактор Э.А.Жирнова
Подп. к печ. . Формат ______. Бумага газетная. Печать офсетная. Печ.л. . Уч.-изд.л. . Тираж ____ экз. Заказ .
Нижегородский государственный технический университет.
Типография НГТУ. 603600, Н. Новгород, ул. Минина, 24.
© Нижегородский государственный технический университет, 2006
Номера страниц
Цель работы
Знакомство с примитивами многопотокового программирования. Получение навыков по объектно- ориентированному программированию многопоточных приложений.
Введение
Объектно-ориентированный подход к программированию позволяет достичь большей безопасности,
масштабируемости и в конечном итоге большей сложности приложений. Организация приложения как набора параллельно (аппаратно на нескольких процессорах или логически) выполняющихся процессов решающих небольшие задачи облегчает воплощение их функциональности. Тем не менее,
асинхронность процессов требует принятия специальных мер по обеспечению корректного обращения к разделяемым ресурсам. Эти ресурсы могут включать в себя различные коммуникационные примитивы, устройства ввода/вывода и т.п.
Появление многозадачной ОС UNIX и языка С сделало доступным многозадачное программирование для приложений через API ОС путём использования системного вызова fork(),
приводящего к разветвлению выполняющегося процесса на два. Недостатком подобной реализации является то, что порождаемые процессы получают копию всего контекста родительского процесса, и потом становятся изолированными от него и друг от друга, что с одной стороны повышает безопасность (независимость) отдельных процессов, а с другой приводит к повышенному использованию системных ресурсов и затрудняет взаимодействие между процессами.
Позже была разработана более экономная и быстродействующая реализация библиотеки для параллельного программирования под названием pthread или POSIX threads. Основным отличием потоков выполнения от процессов является то, что при разветвлении порождённые потоки разделяют один и тот же контекст родительского процесса, что экономит системные ресурсы но вместе с этим делает программирование менее безопасным (деятельность одного потока может нарушить деятельность другого).
Язык С++ и методология объектно-ориентированного программирования позволяет повысить безопасность разрабатываемых приложений за счёт возможности более строгого и формального определения областей видимости и типов данных и определения открытых и внутренних методов.
Тем самым, разработкой и тестированием классов для базовых примитивов параллельного программирования возможно повысить безопасность приложения, их использующего.
Базовые примитивы параллельного программирования
Процесс
Процессом называется последовательный код выполняющийся процессором. В однозадачной ОС
процесс тождественен программе в стадии выполнения. В многозадачной ОС одна и та же программа может быть запущена несколько раз что приводит к существованию нескольких выполняющихся логически параллельно процессов. Процесс может существовать в нескольких состояниях:
активен – когда процессор выполняет код процесса;
блокирован – когда процесс не может выполняться поскольку не соблюдаются условия для его выполнения (процесс читает данные из устройства ввода вывода, которое неготово)
готов для выполнения – когда процесс может выполняться, но процессор выполняет код другого процесса.
Решение о активизации процесса выполняется планировщиком ОС.
Поток выполнения
Поток выполнения, или облегчённый процесс, отличающийся от процесса меньшей изолированностью от других потоков выполнения. Процессы нескольких приложений практически одинаково изолированы друг от друга, в то время как потоки выполнения в пределах одного приложения (процесса ОС) разделяют общие данные и код, а между приложениями полностью
Номера страниц
изолированы друг от друга.
Общие (разделяемые) ресурсы
Данные, код, устройства ввода/вывода, которые могут быть использованы несколькими процессами или потоками выполнения.
Критическая секция
Ресурсы характеризуются количеством, которое увеличивается когда к ним обращаются процессы поставщики ресурсов и уменьшается когда к ним идёт обращение процесса-потребителя ресурса. В
случае, если планировщик процессов произведёт переключение процесса в середине операции увеличения ресурса и начнётся операция по уменьшению ресурса, количество ресурсов может стать некорректным. Участки кода, на которых осуществляется доступ к разделяемым ресурсам при котором может возникнуть их некорректный учёт носит название критической секции.
Мьютекс
Mutex (от Mutable Exclusion) – исключение изменения – коммуникационный примитив,
предназначенный для возможности блокирования потока от выполнения в критической секции процесса. Мьютекс ставится в соответствие ресурсу, и процесс доступа к ресурсу устанавливает блокировку ресурса на время обращения. В конце обращения блокировка снимается, что разблокирует все другие процессы, ожидающие к нему доступа.
Условие (Condition variable)
Способ блокировки, когда процесс может заблокировать (перевести в ожидание выполнения какого- либо условия) сам себя. Для этого вызывается процесс переводит себя в состояние ожидания с помощью функции wait(); процесс, результат деятельности которого может снять блокировку с ожидающего процесса вызовом функции signal() пробуждает спящий процесс. Пример – ресурс временно отсутствует, следовательно процесс потребитель ресурса может находиться в ожидании.
Процесс производитель ресурса посылает сигнал пробуждения после увеличения количества ресурсов.
Синглетон
Глобальный статический объект (данные, код) приложения. Часто с помощью синглетона представляются различные глобальные менеджеры каких либо ресурсов, или сами ресурсы обладающие уникальностью. При многопоточном программировании, когда потоки разделяют общий контекст, синглетон позволяет обращаться к одному и тому же статическому объекту из разных мест приложения, не заботясь о синхронности его создания и инициализации.
Стражник
Guard – реализация мьютекса как автоматической переменной с ограниченным временем жизни.
Используется для повышения компактности описания критических секций, размер которых определяется размером соответствующего блока С++.
РЕАЛИЗАЦИЯ ПРИМИТИВОВ МНОГОПОТОЧНОГО ПРОГРАММИРОВАНИЯ
Файлы splib.h и splib.cpp содержат вариант простейшей библиотеки классов для многопоточного программирования. Для её использования необходимо скопировать их в каталог src проекта (или добавить в проект через automake manager) и включить файл интерфейса splib.h в те файлы, которые будут её использовать. Кроме этого, в параметрах проекта (параметры configure -> LDFLAGS)
необходимо добавить опцию -lpthread для подключения библиотеки POSIX Thread.
Листинг splib.h содержит определение классов библиотеки:
Номера страниц
/***************************************************************************
file: splib.h
Simple pthreads programming lib based on E.Surovegin ESL lib
***************************************************************************/
#ifndef __SPLIB_H_
#define __SPLIB_H_
#include
#include
#include
void splib_init(); // функция инициализации, должна быть вызвана перед использованием
//при старте программы
// класс Мьютекс
class Thread_Mutex
{
public:
Thread_Mutex();
Thread_Mutex(){ ::pthread_mutex_destroy(&mutex_); }
// блокировка
void acquire() const
{ ::pthread_mutex_lock(&mutex_);}
// освобождение
void release() const
{ ::pthread_mutex_unlock(&mutex_);}
private:
mutable pthread_mutex_t mutex_;
};
class Condition
{
public:
Condition();
Condition()
{
::pthread_mutex_destroy(&mutex_);
::pthread_cond_destroy(&cond_);
}
// Mutex-like behavior
// блокировка условной переменной на время её изменения
void acquire() const
{ ::pthread_mutex_lock(&mutex_); }
// оснятие блокировки
void release() const
{ ::pthread_mutex_unlock(&mutex_); }
// Event-like behavior
// вызывает переход процесса в ожидание события/сигнала
void wait() const
{ ::pthread_cond_wait(&cond_, &mutex_); }
// посылает сигнал пробуждения спящему процессу
void signal() const
{ ::pthread_cond_signal(&cond_); }
// посылает сигнал пробуждения всем процессам ожидающим данного условия
void broadcast() const
{ ::pthread_cond_broadcast(&cond_); }
// блокировка с ограничением по времени
/// Returns 0 - if ok, ETIMEDOUT or EINTR
int timed_wait(
const timespec* abstime) const
{
return ::pthread_cond_timedwait(&cond_, &mutex_, abstime);
}
private:
mutable pthread_cond_t cond_;
mutable pthread_mutex_t mutex_;
};
// Semaphores aren't actually part of the Pthreads standard.
Номера страниц
// В данной имплементации Семафор – примитив, который разрешает процессу продолжать
// выполнение если значение семафора (sem_) больше нуля, и приводит к блокировке
// если его значение равно нулю до момента когда его значение увеличится.
class Semaphore
{
public:
explicit Semaphore(size_t
/*maximum_count - ignored*/
=
0
, size_t initial_count =
0
,
const char
*
/*name - ignored*/
=
0
)
{
// Linux supports only thread semaphores (pshared == 0)
::sem_init(&sem_,
0
, initial_count);
}
Semaphore(){ ::sem_destroy(&sem_); }
void acquire() const
{ ::sem_wait(&sem_); }
void release() const
{ ::sem_post(&sem_); }
void wait() const
{ acquire(); }
private:
mutable sem_t sem_;
};
// Стражник – шаблон в качестве параметра которого может использоваться Мьютекс
// для создания автоматических объектов защищающих критические секции процессов
template <typename SYNC_OBJ>
class Guard
{
public:
Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
// блокировка
lock_.acquire();
}
// освобождение
Guard(){ lock_.release(); }
private:
const
SYNC_OBJ& lock_;
};
// Стражник с инверсным поведением
template <typename SYNC_OBJ>
class Reverse_Guard
{
public:
Reverse_Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
lock_.release();
}
Reverse_Guard(){ lock_.acquire(); }
private:
const
SYNC_OBJ& lock_;
};
// Синглетон – шаблон для создания глобальных объектов
// класс, определяющий функциональность объекта должен включать
// friend class Singleton
// для сокращения записи вызов можно осуществлять определив
// #define theClassName Singleton::instance()
// theClassName->method()
template<typename T>
class Singleton
{
Номера страниц
public:
// возвращает указатель на статический объект Т, если объекта не существует вызывает
// закрытый конструктор этого объекта через функцию create_instance()
static
T* instance()
{
if (!instance_)
create_instance();
return instance_;
}
private:
// конструктор не может быть вызван за пределами класса
Singleton() {}
Singleton() {}
static void create_instance();
static void close();
// статическая переменная, хранящая указатель на однократно созданный объект
static
T* instance_;
// мьютекс для блокировки объекта на время вызова конструктора класса
static thread_mutex lock_;
};
// template members
// исходная инициализация статического указателя на объект
template<typename T>
T* Singleton::instance_ =
0
;
template<typename T>
thread_mutex Singleton::lock_ = PTHREAD_MUTEX_INITIALIZER;
// объект создаётся лишь однократно
template<typename T>
void
Singleton::create_instance()
{
Guard guard(lock_);
if (!instance_)
{
instance_ = new T();
}
}
template<class T>
void
Singleton::close()
{
delete instance_;
instance_ =
0
;
}
// абстрактный класс PThread
class PThread
{
public:
void start(); // функция активации потока выполнения, вызывается из конструктора
// порождённого класса
void stop();
void join();
// Gracefull stop support
void signal_stop();
bool stop_requested() const
;
bool stopped() const
;
// Only for dynamic allocated instances
void destroy();
void delayed_destroy();
int priority() const
;
Номера страниц
void priority(
int
) const
;
unsigned id() const
{ return id_; }
const char
* name() const
{ return name_; }
// RT scheduler support
static int rt_max_priority();
static int rt_min_priority();
int rt_get_priority() const
;
void rt_set_priority(
int
);
protected:
// конструктор – защищённый, поэтому вызывается он только из порождённых классов
explicit PThread(
const char
* name =
""
);
virtual PThread();
// абстрактная функция, определяющая функциональность потока выполнения.
// должна быть реализована в порождённых классах
virtual void execute() =
0
;
private:
enum state { ts_waiting, ts_running, ts_stopping, ts_stopped };
static void
* __thread_proc(
void
*);
private:
Condition cond_;
pthread_t id_;
char name_[
20
]; volatile state state_;
int pipe_fd_[
2
];
bool auto_delete_;
};
#endif
// __SPLIB_H_
Задания и порядок их выполнения
1. Загрузите Linux и войдите под именем и паролем пользователя, выданного вам преподавателем.
2. Загрузите KDE, осуществив при необходимости необходимые настройки (язык, страна)
3. В домашнем каталоге создайте каталог $HOME/Design/lab1 4. Запустите Kdevelop и создайте проект test1 (проект типа Simple C++ application). Добавьте к проекту файлы splib.cpp и sblib.h. Попробуйте скомпилировать проект и исправить возможные ошибки.
5. Реализуйте поток выполнения, который в бесконечном цикле раз в секунду (sleep(1))
инкрементирует внутреннюю переменную и выводит в стандартный поток вывода (cout << ..) её
значение и имя потока. Начальное значение переменной должно задаваться в конструкторе класса. Создайте 2 объекта этого класса с именами thread1 и thread2 и различными начальными значениями внутренней переменной (в main()). После создания этих двух потоков вызовите функцию ожидания (sleep(20)), скомпилируйте и отладьте программу. Пронаблюдайте выводимые данные.
6. Test2: Измените класс, реализующий ваш поток так, чтобы внутренняя переменная стала статической. Пронаблюдайте как изменился вывод программы.
7. Test3: С помощью синглетона реализуйте глобальную переменную с нулевым начальным значением, с методами инкремента, декремента и чтения этой переменной. Реализуйте два класса для потоков, один из которых в бесконечном цикле инкрементирует эту переменную, а второй –
декрементирует. Напишите тест, в котором будут выдаваться сообщения если значение переменной будут больше 2 и меньше -2. Попробуйте понаблюдать за поведением программы.
Номера страниц
8. Test4: используя класс Condition реализуйте такое поведение глобального объекта, при котором переменная не может декрементироваться в случае если её значение меньше 0.
Номера страниц
Министерство образования Российской Федерации
НИЖЕГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ
УНИВЕРСИТЕТ
Кафедра «Информационные радиосистемы»
Реализация многопоточных приложений на С++
Методические указания к лабораторной работе № 1
по дисциплине «Дисциплина по выбору 1»
для студентов специальностей 200700 «Радиотехника»
Нижний Новгород 2006
Номера страниц
УДК 621.325.5-181.4
Реализация многопоточных приложений на С++: Метод. указания к лаб. работе № 1
по дисциплине «Сетевые информационные технологии» для студентов спец. 200700
/ НГТУ; Сост.: А.В.Миронов Н. Новгород, 2006 ??с.
Изложены начальные сведения о реализации многопоточных приложений с использованием объектно-ориентированного программирования. Сформулированы задания и порядок выполнения для лабораторной работы.
Редактор Э.А.Жирнова
Подп. к печ. . Формат ______. Бумага газетная. Печать офсетная. Печ.л. . Уч.-изд.л. . Тираж ____ экз. Заказ .
Нижегородский государственный технический университет.
Типография НГТУ. 603600, Н. Новгород, ул. Минина, 24.
© Нижегородский государственный технический университет, 2006
Номера страниц
Цель работы
Знакомство с примитивами многопотокового программирования. Получение навыков по объектно- ориентированному программированию многопоточных приложений.
Введение
Объектно-ориентированный подход к программированию позволяет достичь большей безопасности,
масштабируемости и в конечном итоге большей сложности приложений. Организация приложения как набора параллельно (аппаратно на нескольких процессорах или логически) выполняющихся процессов решающих небольшие задачи облегчает воплощение их функциональности. Тем не менее,
асинхронность процессов требует принятия специальных мер по обеспечению корректного обращения к разделяемым ресурсам. Эти ресурсы могут включать в себя различные коммуникационные примитивы, устройства ввода/вывода и т.п.
Появление многозадачной ОС UNIX и языка С сделало доступным многозадачное программирование для приложений через API ОС путём использования системного вызова fork(),
приводящего к разветвлению выполняющегося процесса на два. Недостатком подобной реализации является то, что порождаемые процессы получают копию всего контекста родительского процесса, и потом становятся изолированными от него и друг от друга, что с одной стороны повышает безопасность (независимость) отдельных процессов, а с другой приводит к повышенному использованию системных ресурсов и затрудняет взаимодействие между процессами.
Позже была разработана более экономная и быстродействующая реализация библиотеки для параллельного программирования под названием pthread или POSIX threads. Основным отличием потоков выполнения от процессов является то, что при разветвлении порождённые потоки разделяют один и тот же контекст родительского процесса, что экономит системные ресурсы но вместе с этим делает программирование менее безопасным (деятельность одного потока может нарушить деятельность другого).
Язык С++ и методология объектно-ориентированного программирования позволяет повысить безопасность разрабатываемых приложений за счёт возможности более строгого и формального определения областей видимости и типов данных и определения открытых и внутренних методов.
Тем самым, разработкой и тестированием классов для базовых примитивов параллельного программирования возможно повысить безопасность приложения, их использующего.
Базовые примитивы параллельного программирования
Процесс
Процессом называется последовательный код выполняющийся процессором. В однозадачной ОС
процесс тождественен программе в стадии выполнения. В многозадачной ОС одна и та же программа может быть запущена несколько раз что приводит к существованию нескольких выполняющихся логически параллельно процессов. Процесс может существовать в нескольких состояниях:
активен – когда процессор выполняет код процесса;
блокирован – когда процесс не может выполняться поскольку не соблюдаются условия для его выполнения (процесс читает данные из устройства ввода вывода, которое неготово)
готов для выполнения – когда процесс может выполняться, но процессор выполняет код другого процесса.
Решение о активизации процесса выполняется планировщиком ОС.
Поток выполнения
Поток выполнения, или облегчённый процесс, отличающийся от процесса меньшей изолированностью от других потоков выполнения. Процессы нескольких приложений практически одинаково изолированы друг от друга, в то время как потоки выполнения в пределах одного приложения (процесса ОС) разделяют общие данные и код, а между приложениями полностью
Номера страниц
изолированы друг от друга.
Общие (разделяемые) ресурсы
Данные, код, устройства ввода/вывода, которые могут быть использованы несколькими процессами или потоками выполнения.
Критическая секция
Ресурсы характеризуются количеством, которое увеличивается когда к ним обращаются процессы поставщики ресурсов и уменьшается когда к ним идёт обращение процесса-потребителя ресурса. В
случае, если планировщик процессов произведёт переключение процесса в середине операции увеличения ресурса и начнётся операция по уменьшению ресурса, количество ресурсов может стать некорректным. Участки кода, на которых осуществляется доступ к разделяемым ресурсам при котором может возникнуть их некорректный учёт носит название критической секции.
Мьютекс
Mutex (от Mutable Exclusion) – исключение изменения – коммуникационный примитив,
предназначенный для возможности блокирования потока от выполнения в критической секции процесса. Мьютекс ставится в соответствие ресурсу, и процесс доступа к ресурсу устанавливает блокировку ресурса на время обращения. В конце обращения блокировка снимается, что разблокирует все другие процессы, ожидающие к нему доступа.
Условие (Condition variable)
Способ блокировки, когда процесс может заблокировать (перевести в ожидание выполнения какого- либо условия) сам себя. Для этого вызывается процесс переводит себя в состояние ожидания с помощью функции wait(); процесс, результат деятельности которого может снять блокировку с ожидающего процесса вызовом функции signal() пробуждает спящий процесс. Пример – ресурс временно отсутствует, следовательно процесс потребитель ресурса может находиться в ожидании.
Процесс производитель ресурса посылает сигнал пробуждения после увеличения количества ресурсов.
Синглетон
Глобальный статический объект (данные, код) приложения. Часто с помощью синглетона представляются различные глобальные менеджеры каких либо ресурсов, или сами ресурсы обладающие уникальностью. При многопоточном программировании, когда потоки разделяют общий контекст, синглетон позволяет обращаться к одному и тому же статическому объекту из разных мест приложения, не заботясь о синхронности его создания и инициализации.
Стражник
Guard – реализация мьютекса как автоматической переменной с ограниченным временем жизни.
Используется для повышения компактности описания критических секций, размер которых определяется размером соответствующего блока С++.
РЕАЛИЗАЦИЯ ПРИМИТИВОВ МНОГОПОТОЧНОГО ПРОГРАММИРОВАНИЯ
Файлы splib.h и splib.cpp содержат вариант простейшей библиотеки классов для многопоточного программирования. Для её использования необходимо скопировать их в каталог src проекта (или добавить в проект через automake manager) и включить файл интерфейса splib.h в те файлы, которые будут её использовать. Кроме этого, в параметрах проекта (параметры configure -> LDFLAGS)
необходимо добавить опцию -lpthread для подключения библиотеки POSIX Thread.
Листинг splib.h содержит определение классов библиотеки:
Номера страниц
/***************************************************************************
file: splib.h
Simple pthreads programming lib based on E.Surovegin ESL lib
***************************************************************************/
#ifndef __SPLIB_H_
#define __SPLIB_H_
#include
#include
#include
void splib_init(); // функция инициализации, должна быть вызвана перед использованием
//при старте программы
// класс Мьютекс
class Thread_Mutex
{
public:
Thread_Mutex();
Thread_Mutex(){ ::pthread_mutex_destroy(&mutex_); }
// блокировка
void acquire() const
{ ::pthread_mutex_lock(&mutex_);}
// освобождение
void release() const
{ ::pthread_mutex_unlock(&mutex_);}
private:
mutable pthread_mutex_t mutex_;
};
class Condition
{
public:
Condition();
Condition()
{
::pthread_mutex_destroy(&mutex_);
::pthread_cond_destroy(&cond_);
}
// Mutex-like behavior
// блокировка условной переменной на время её изменения
void acquire() const
{ ::pthread_mutex_lock(&mutex_); }
// оснятие блокировки
void release() const
{ ::pthread_mutex_unlock(&mutex_); }
// Event-like behavior
// вызывает переход процесса в ожидание события/сигнала
void wait() const
{ ::pthread_cond_wait(&cond_, &mutex_); }
// посылает сигнал пробуждения спящему процессу
void signal() const
{ ::pthread_cond_signal(&cond_); }
// посылает сигнал пробуждения всем процессам ожидающим данного условия
void broadcast() const
{ ::pthread_cond_broadcast(&cond_); }
// блокировка с ограничением по времени
/// Returns 0 - if ok, ETIMEDOUT or EINTR
int timed_wait(
const timespec* abstime) const
{
return ::pthread_cond_timedwait(&cond_, &mutex_, abstime);
}
private:
mutable pthread_cond_t cond_;
mutable pthread_mutex_t mutex_;
};
// Semaphores aren't actually part of the Pthreads standard.
Номера страниц
// В данной имплементации Семафор – примитив, который разрешает процессу продолжать
// выполнение если значение семафора (sem_) больше нуля, и приводит к блокировке
// если его значение равно нулю до момента когда его значение увеличится.
class Semaphore
{
public:
explicit Semaphore(size_t
/*maximum_count - ignored*/
=
0
, size_t initial_count =
0
,
const char
*
/*name - ignored*/
=
0
)
{
// Linux supports only thread semaphores (pshared == 0)
::sem_init(&sem_,
0
, initial_count);
}
Semaphore(){ ::sem_destroy(&sem_); }
void acquire() const
{ ::sem_wait(&sem_); }
void release() const
{ ::sem_post(&sem_); }
void wait() const
{ acquire(); }
private:
mutable sem_t sem_;
};
// Стражник – шаблон в качестве параметра которого может использоваться Мьютекс
// для создания автоматических объектов защищающих критические секции процессов
template <typename SYNC_OBJ>
class Guard
{
public:
Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
// блокировка
lock_.acquire();
}
// освобождение
Guard(){ lock_.release(); }
private:
const
SYNC_OBJ& lock_;
};
// Стражник с инверсным поведением
template <typename SYNC_OBJ>
class Reverse_Guard
{
public:
Reverse_Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
lock_.release();
}
Reverse_Guard(){ lock_.acquire(); }
private:
const
SYNC_OBJ& lock_;
};
// Синглетон – шаблон для создания глобальных объектов
// класс, определяющий функциональность объекта должен включать
// friend class Singleton
// для сокращения записи вызов можно осуществлять определив
// #define theClassName Singleton::instance()
// theClassName->method()
template<typename T>
class Singleton
{
Номера страниц
public:
// возвращает указатель на статический объект Т, если объекта не существует вызывает
// закрытый конструктор этого объекта через функцию create_instance()
static
T* instance()
{
if (!instance_)
create_instance();
return instance_;
}
private:
// конструктор не может быть вызван за пределами класса
Singleton() {}
Singleton() {}
static void create_instance();
static void close();
// статическая переменная, хранящая указатель на однократно созданный объект
static
T* instance_;
// мьютекс для блокировки объекта на время вызова конструктора класса
static thread_mutex lock_;
};
// template members
// исходная инициализация статического указателя на объект
template<typename T>
T* Singleton::instance_ =
0
;
template<typename T>
thread_mutex Singleton::lock_ = PTHREAD_MUTEX_INITIALIZER;
// объект создаётся лишь однократно
template<typename T>
void
Singleton::create_instance()
{
Guard guard(lock_);
if (!instance_)
{
instance_ = new T();
}
}
template<class T>
void
Singleton::close()
{
delete instance_;
instance_ =
0
;
}
// абстрактный класс PThread
class PThread
{
public:
void start(); // функция активации потока выполнения, вызывается из конструктора
// порождённого класса
void stop();
void join();
// Gracefull stop support
void signal_stop();
bool stop_requested() const
;
bool stopped() const
;
// Only for dynamic allocated instances
void destroy();
void delayed_destroy();
int priority() const
;
Номера страниц
void priority(
int
) const
;
unsigned id() const
{ return id_; }
const char
* name() const
{ return name_; }
// RT scheduler support
static int rt_max_priority();
static int rt_min_priority();
int rt_get_priority() const
;
void rt_set_priority(
int
);
protected:
// конструктор – защищённый, поэтому вызывается он только из порождённых классов
explicit PThread(
const char
* name =
""
);
virtual PThread();
// абстрактная функция, определяющая функциональность потока выполнения.
// должна быть реализована в порождённых классах
virtual void execute() =
0
;
private:
enum state { ts_waiting, ts_running, ts_stopping, ts_stopped };
static void
* __thread_proc(
void
*);
private:
Condition cond_;
pthread_t id_;
char name_[
20
]; volatile state state_;
int pipe_fd_[
2
];
bool auto_delete_;
};
#endif
// __SPLIB_H_
Задания и порядок их выполнения
1. Загрузите Linux и войдите под именем и паролем пользователя, выданного вам преподавателем.
2. Загрузите KDE, осуществив при необходимости необходимые настройки (язык, страна)
3. В домашнем каталоге создайте каталог $HOME/Design/lab1 4. Запустите Kdevelop и создайте проект test1 (проект типа Simple C++ application). Добавьте к проекту файлы splib.cpp и sblib.h. Попробуйте скомпилировать проект и исправить возможные ошибки.
5. Реализуйте поток выполнения, который в бесконечном цикле раз в секунду (sleep(1))
инкрементирует внутреннюю переменную и выводит в стандартный поток вывода (cout << ..) её
значение и имя потока. Начальное значение переменной должно задаваться в конструкторе класса. Создайте 2 объекта этого класса с именами thread1 и thread2 и различными начальными значениями внутренней переменной (в main()). После создания этих двух потоков вызовите функцию ожидания (sleep(20)), скомпилируйте и отладьте программу. Пронаблюдайте выводимые данные.
6. Test2: Измените класс, реализующий ваш поток так, чтобы внутренняя переменная стала статической. Пронаблюдайте как изменился вывод программы.
7. Test3: С помощью синглетона реализуйте глобальную переменную с нулевым начальным значением, с методами инкремента, декремента и чтения этой переменной. Реализуйте два класса для потоков, один из которых в бесконечном цикле инкрементирует эту переменную, а второй –
декрементирует. Напишите тест, в котором будут выдаваться сообщения если значение переменной будут больше 2 и меньше -2. Попробуйте понаблюдать за поведением программы.
Номера страниц
8. Test4: используя класс Condition реализуйте такое поведение глобального объекта, при котором переменная не может декрементироваться в случае если её значение меньше 0.
Номера страниц
Министерство образования Российской Федерации
НИЖЕГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ
УНИВЕРСИТЕТ
Кафедра «Информационные радиосистемы»
Реализация многопоточных приложений на С++
Методические указания к лабораторной работе № 1
по дисциплине «Дисциплина по выбору 1»
для студентов специальностей 200700 «Радиотехника»
Нижний Новгород 2006
Номера страниц
УДК 621.325.5-181.4
Реализация многопоточных приложений на С++: Метод. указания к лаб. работе № 1
по дисциплине «Сетевые информационные технологии» для студентов спец. 200700
/ НГТУ; Сост.: А.В.Миронов Н. Новгород, 2006 ??с.
Изложены начальные сведения о реализации многопоточных приложений с использованием объектно-ориентированного программирования. Сформулированы задания и порядок выполнения для лабораторной работы.
Редактор Э.А.Жирнова
Подп. к печ. . Формат ______. Бумага газетная. Печать офсетная. Печ.л. . Уч.-изд.л. . Тираж ____ экз. Заказ .
Нижегородский государственный технический университет.
Типография НГТУ. 603600, Н. Новгород, ул. Минина, 24.
© Нижегородский государственный технический университет, 2006
Номера страниц
Цель работы
Знакомство с примитивами многопотокового программирования. Получение навыков по объектно- ориентированному программированию многопоточных приложений.
Введение
Объектно-ориентированный подход к программированию позволяет достичь большей безопасности,
масштабируемости и в конечном итоге большей сложности приложений. Организация приложения как набора параллельно (аппаратно на нескольких процессорах или логически) выполняющихся процессов решающих небольшие задачи облегчает воплощение их функциональности. Тем не менее,
асинхронность процессов требует принятия специальных мер по обеспечению корректного обращения к разделяемым ресурсам. Эти ресурсы могут включать в себя различные коммуникационные примитивы, устройства ввода/вывода и т.п.
Появление многозадачной ОС UNIX и языка С сделало доступным многозадачное программирование для приложений через API ОС путём использования системного вызова fork(),
приводящего к разветвлению выполняющегося процесса на два. Недостатком подобной реализации является то, что порождаемые процессы получают копию всего контекста родительского процесса, и потом становятся изолированными от него и друг от друга, что с одной стороны повышает безопасность (независимость) отдельных процессов, а с другой приводит к повышенному использованию системных ресурсов и затрудняет взаимодействие между процессами.
Позже была разработана более экономная и быстродействующая реализация библиотеки для параллельного программирования под названием pthread или POSIX threads. Основным отличием потоков выполнения от процессов является то, что при разветвлении порождённые потоки разделяют один и тот же контекст родительского процесса, что экономит системные ресурсы но вместе с этим делает программирование менее безопасным (деятельность одного потока может нарушить деятельность другого).
Язык С++ и методология объектно-ориентированного программирования позволяет повысить безопасность разрабатываемых приложений за счёт возможности более строгого и формального определения областей видимости и типов данных и определения открытых и внутренних методов.
Тем самым, разработкой и тестированием классов для базовых примитивов параллельного программирования возможно повысить безопасность приложения, их использующего.
Базовые примитивы параллельного программирования
Процесс
Процессом называется последовательный код выполняющийся процессором. В однозадачной ОС
процесс тождественен программе в стадии выполнения. В многозадачной ОС одна и та же программа может быть запущена несколько раз что приводит к существованию нескольких выполняющихся логически параллельно процессов. Процесс может существовать в нескольких состояниях:
активен – когда процессор выполняет код процесса;
блокирован – когда процесс не может выполняться поскольку не соблюдаются условия для его выполнения (процесс читает данные из устройства ввода вывода, которое неготово)
готов для выполнения – когда процесс может выполняться, но процессор выполняет код другого процесса.
Решение о активизации процесса выполняется планировщиком ОС.
Поток выполнения
Поток выполнения, или облегчённый процесс, отличающийся от процесса меньшей изолированностью от других потоков выполнения. Процессы нескольких приложений практически одинаково изолированы друг от друга, в то время как потоки выполнения в пределах одного приложения (процесса ОС) разделяют общие данные и код, а между приложениями полностью
Номера страниц
изолированы друг от друга.
Общие (разделяемые) ресурсы
Данные, код, устройства ввода/вывода, которые могут быть использованы несколькими процессами или потоками выполнения.
Критическая секция
Ресурсы характеризуются количеством, которое увеличивается когда к ним обращаются процессы поставщики ресурсов и уменьшается когда к ним идёт обращение процесса-потребителя ресурса. В
случае, если планировщик процессов произведёт переключение процесса в середине операции увеличения ресурса и начнётся операция по уменьшению ресурса, количество ресурсов может стать некорректным. Участки кода, на которых осуществляется доступ к разделяемым ресурсам при котором может возникнуть их некорректный учёт носит название критической секции.
Мьютекс
Mutex (от Mutable Exclusion) – исключение изменения – коммуникационный примитив,
предназначенный для возможности блокирования потока от выполнения в критической секции процесса. Мьютекс ставится в соответствие ресурсу, и процесс доступа к ресурсу устанавливает блокировку ресурса на время обращения. В конце обращения блокировка снимается, что разблокирует все другие процессы, ожидающие к нему доступа.
Условие (Condition variable)
Способ блокировки, когда процесс может заблокировать (перевести в ожидание выполнения какого- либо условия) сам себя. Для этого вызывается процесс переводит себя в состояние ожидания с помощью функции wait(); процесс, результат деятельности которого может снять блокировку с ожидающего процесса вызовом функции signal() пробуждает спящий процесс. Пример – ресурс временно отсутствует, следовательно процесс потребитель ресурса может находиться в ожидании.
Процесс производитель ресурса посылает сигнал пробуждения после увеличения количества ресурсов.
Синглетон
Глобальный статический объект (данные, код) приложения. Часто с помощью синглетона представляются различные глобальные менеджеры каких либо ресурсов, или сами ресурсы обладающие уникальностью. При многопоточном программировании, когда потоки разделяют общий контекст, синглетон позволяет обращаться к одному и тому же статическому объекту из разных мест приложения, не заботясь о синхронности его создания и инициализации.
Стражник
Guard – реализация мьютекса как автоматической переменной с ограниченным временем жизни.
Используется для повышения компактности описания критических секций, размер которых определяется размером соответствующего блока С++.
РЕАЛИЗАЦИЯ ПРИМИТИВОВ МНОГОПОТОЧНОГО ПРОГРАММИРОВАНИЯ
Файлы splib.h и splib.cpp содержат вариант простейшей библиотеки классов для многопоточного программирования. Для её использования необходимо скопировать их в каталог src проекта (или добавить в проект через automake manager) и включить файл интерфейса splib.h в те файлы, которые будут её использовать. Кроме этого, в параметрах проекта (параметры configure -> LDFLAGS)
необходимо добавить опцию -lpthread для подключения библиотеки POSIX Thread.
Листинг splib.h содержит определение классов библиотеки:
Номера страниц
/***************************************************************************
file: splib.h
Simple pthreads programming lib based on E.Surovegin ESL lib
***************************************************************************/
#ifndef __SPLIB_H_
#define __SPLIB_H_
#include
#include
#include
void splib_init(); // функция инициализации, должна быть вызвана перед использованием
//при старте программы
// класс Мьютекс
class Thread_Mutex
{
public:
Thread_Mutex();
Thread_Mutex(){ ::pthread_mutex_destroy(&mutex_); }
// блокировка
void acquire() const
{ ::pthread_mutex_lock(&mutex_);}
// освобождение
void release() const
{ ::pthread_mutex_unlock(&mutex_);}
private:
mutable pthread_mutex_t mutex_;
};
class Condition
{
public:
Condition();
Condition()
{
::pthread_mutex_destroy(&mutex_);
::pthread_cond_destroy(&cond_);
}
// Mutex-like behavior
// блокировка условной переменной на время её изменения
void acquire() const
{ ::pthread_mutex_lock(&mutex_); }
// оснятие блокировки
void release() const
{ ::pthread_mutex_unlock(&mutex_); }
// Event-like behavior
// вызывает переход процесса в ожидание события/сигнала
void wait() const
{ ::pthread_cond_wait(&cond_, &mutex_); }
// посылает сигнал пробуждения спящему процессу
void signal() const
{ ::pthread_cond_signal(&cond_); }
// посылает сигнал пробуждения всем процессам ожидающим данного условия
void broadcast() const
{ ::pthread_cond_broadcast(&cond_); }
// блокировка с ограничением по времени
/// Returns 0 - if ok, ETIMEDOUT or EINTR
int timed_wait(
const timespec* abstime) const
{
return ::pthread_cond_timedwait(&cond_, &mutex_, abstime);
}
private:
mutable pthread_cond_t cond_;
mutable pthread_mutex_t mutex_;
};
// Semaphores aren't actually part of the Pthreads standard.
Номера страниц
// В данной имплементации Семафор – примитив, который разрешает процессу продолжать
// выполнение если значение семафора (sem_) больше нуля, и приводит к блокировке
// если его значение равно нулю до момента когда его значение увеличится.
class Semaphore
{
public:
explicit Semaphore(size_t
/*maximum_count - ignored*/
=
0
, size_t initial_count =
0
,
const char
*
/*name - ignored*/
=
0
)
{
// Linux supports only thread semaphores (pshared == 0)
::sem_init(&sem_,
0
, initial_count);
}
Semaphore(){ ::sem_destroy(&sem_); }
void acquire() const
{ ::sem_wait(&sem_); }
void release() const
{ ::sem_post(&sem_); }
void wait() const
{ acquire(); }
private:
mutable sem_t sem_;
};
// Стражник – шаблон в качестве параметра которого может использоваться Мьютекс
// для создания автоматических объектов защищающих критические секции процессов
template <typename SYNC_OBJ>
class Guard
{
public:
Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
// блокировка
lock_.acquire();
}
// освобождение
Guard(){ lock_.release(); }
private:
const
SYNC_OBJ& lock_;
};
// Стражник с инверсным поведением
template <typename SYNC_OBJ>
class Reverse_Guard
{
public:
Reverse_Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
lock_.release();
}
Reverse_Guard(){ lock_.acquire(); }
private:
const
SYNC_OBJ& lock_;
};
// Синглетон – шаблон для создания глобальных объектов
// класс, определяющий функциональность объекта должен включать
// friend class Singleton
// для сокращения записи вызов можно осуществлять определив
// #define theClassName Singleton::instance()
// theClassName->method()
template<typename T>
class Singleton
{
Номера страниц
public:
// возвращает указатель на статический объект Т, если объекта не существует вызывает
// закрытый конструктор этого объекта через функцию create_instance()
static
T* instance()
{
if (!instance_)
create_instance();
return instance_;
}
private:
// конструктор не может быть вызван за пределами класса
Singleton() {}
Singleton() {}
static void create_instance();
static void close();
// статическая переменная, хранящая указатель на однократно созданный объект
static
T* instance_;
// мьютекс для блокировки объекта на время вызова конструктора класса
static thread_mutex lock_;
};
// template members
// исходная инициализация статического указателя на объект
template<typename T>
T* Singleton::instance_ =
0
;
template<typename T>
thread_mutex Singleton::lock_ = PTHREAD_MUTEX_INITIALIZER;
// объект создаётся лишь однократно
template<typename T>
void
Singleton::create_instance()
{
Guard guard(lock_);
if (!instance_)
{
instance_ = new T();
}
}
template<class T>
void
Singleton::close()
{
delete instance_;
instance_ =
0
;
}
// абстрактный класс PThread
class PThread
{
public:
void start(); // функция активации потока выполнения, вызывается из конструктора
// порождённого класса
void stop();
void join();
// Gracefull stop support
void signal_stop();
bool stop_requested() const
;
bool stopped() const
;
// Only for dynamic allocated instances
void destroy();
void delayed_destroy();
int priority() const
;
Номера страниц
void priority(
int
) const
;
unsigned id() const
{ return id_; }
const char
* name() const
{ return name_; }
// RT scheduler support
static int rt_max_priority();
static int rt_min_priority();
int rt_get_priority() const
;
void rt_set_priority(
int
);
protected:
// конструктор – защищённый, поэтому вызывается он только из порождённых классов
explicit PThread(
const char
* name =
""
);
virtual PThread();
// абстрактная функция, определяющая функциональность потока выполнения.
// должна быть реализована в порождённых классах
virtual void execute() =
0
;
private:
enum state { ts_waiting, ts_running, ts_stopping, ts_stopped };
static void
* __thread_proc(
void
*);
private:
Condition cond_;
pthread_t id_;
char name_[
20
]; volatile state state_;
int pipe_fd_[
2
];
bool auto_delete_;
};
#endif
// __SPLIB_H_
Задания и порядок их выполнения
1. Загрузите Linux и войдите под именем и паролем пользователя, выданного вам преподавателем.
2. Загрузите KDE, осуществив при необходимости необходимые настройки (язык, страна)
3. В домашнем каталоге создайте каталог $HOME/Design/lab1 4. Запустите Kdevelop и создайте проект test1 (проект типа Simple C++ application). Добавьте к проекту файлы splib.cpp и sblib.h. Попробуйте скомпилировать проект и исправить возможные ошибки.
5. Реализуйте поток выполнения, который в бесконечном цикле раз в секунду (sleep(1))
инкрементирует внутреннюю переменную и выводит в стандартный поток вывода (cout << ..) её
значение и имя потока. Начальное значение переменной должно задаваться в конструкторе класса. Создайте 2 объекта этого класса с именами thread1 и thread2 и различными начальными значениями внутренней переменной (в main()). После создания этих двух потоков вызовите функцию ожидания (sleep(20)), скомпилируйте и отладьте программу. Пронаблюдайте выводимые данные.
6. Test2: Измените класс, реализующий ваш поток так, чтобы внутренняя переменная стала статической. Пронаблюдайте как изменился вывод программы.
7. Test3: С помощью синглетона реализуйте глобальную переменную с нулевым начальным значением, с методами инкремента, декремента и чтения этой переменной. Реализуйте два класса для потоков, один из которых в бесконечном цикле инкрементирует эту переменную, а второй –
декрементирует. Напишите тест, в котором будут выдаваться сообщения если значение переменной будут больше 2 и меньше -2. Попробуйте понаблюдать за поведением программы.
Номера страниц
8. Test4: используя класс Condition реализуйте такое поведение глобального объекта, при котором переменная не может декрементироваться в случае если её значение меньше 0.
Номера страниц
Министерство образования Российской Федерации
НИЖЕГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ
УНИВЕРСИТЕТ
Кафедра «Информационные радиосистемы»
Реализация многопоточных приложений на С++
Методические указания к лабораторной работе № 1
по дисциплине «Дисциплина по выбору 1»
для студентов специальностей 200700 «Радиотехника»
Нижний Новгород 2006
Номера страниц
УДК 621.325.5-181.4
Реализация многопоточных приложений на С++: Метод. указания к лаб. работе № 1
по дисциплине «Сетевые информационные технологии» для студентов спец. 200700
/ НГТУ; Сост.: А.В.Миронов Н. Новгород, 2006 ??с.
Изложены начальные сведения о реализации многопоточных приложений с использованием объектно-ориентированного программирования. Сформулированы задания и порядок выполнения для лабораторной работы.
Редактор Э.А.Жирнова
Подп. к печ. . Формат ______. Бумага газетная. Печать офсетная. Печ.л. . Уч.-изд.л. . Тираж ____ экз. Заказ .
Нижегородский государственный технический университет.
Типография НГТУ. 603600, Н. Новгород, ул. Минина, 24.
© Нижегородский государственный технический университет, 2006
Номера страниц
Цель работы
Знакомство с примитивами многопотокового программирования. Получение навыков по объектно- ориентированному программированию многопоточных приложений.
Введение
Объектно-ориентированный подход к программированию позволяет достичь большей безопасности,
масштабируемости и в конечном итоге большей сложности приложений. Организация приложения как набора параллельно (аппаратно на нескольких процессорах или логически) выполняющихся процессов решающих небольшие задачи облегчает воплощение их функциональности. Тем не менее,
асинхронность процессов требует принятия специальных мер по обеспечению корректного обращения к разделяемым ресурсам. Эти ресурсы могут включать в себя различные коммуникационные примитивы, устройства ввода/вывода и т.п.
Появление многозадачной ОС UNIX и языка С сделало доступным многозадачное программирование для приложений через API ОС путём использования системного вызова fork(),
приводящего к разветвлению выполняющегося процесса на два. Недостатком подобной реализации является то, что порождаемые процессы получают копию всего контекста родительского процесса, и потом становятся изолированными от него и друг от друга, что с одной стороны повышает безопасность (независимость) отдельных процессов, а с другой приводит к повышенному использованию системных ресурсов и затрудняет взаимодействие между процессами.
Позже была разработана более экономная и быстродействующая реализация библиотеки для параллельного программирования под названием pthread или POSIX threads. Основным отличием потоков выполнения от процессов является то, что при разветвлении порождённые потоки разделяют один и тот же контекст родительского процесса, что экономит системные ресурсы но вместе с этим делает программирование менее безопасным (деятельность одного потока может нарушить деятельность другого).
Язык С++ и методология объектно-ориентированного программирования позволяет повысить безопасность разрабатываемых приложений за счёт возможности более строгого и формального определения областей видимости и типов данных и определения открытых и внутренних методов.
Тем самым, разработкой и тестированием классов для базовых примитивов параллельного программирования возможно повысить безопасность приложения, их использующего.
Базовые примитивы параллельного программирования
Процесс
Процессом называется последовательный код выполняющийся процессором. В однозадачной ОС
процесс тождественен программе в стадии выполнения. В многозадачной ОС одна и та же программа может быть запущена несколько раз что приводит к существованию нескольких выполняющихся логически параллельно процессов. Процесс может существовать в нескольких состояниях:
активен – когда процессор выполняет код процесса;
блокирован – когда процесс не может выполняться поскольку не соблюдаются условия для его выполнения (процесс читает данные из устройства ввода вывода, которое неготово)
готов для выполнения – когда процесс может выполняться, но процессор выполняет код другого процесса.
Решение о активизации процесса выполняется планировщиком ОС.
Поток выполнения
Поток выполнения, или облегчённый процесс, отличающийся от процесса меньшей изолированностью от других потоков выполнения. Процессы нескольких приложений практически одинаково изолированы друг от друга, в то время как потоки выполнения в пределах одного приложения (процесса ОС) разделяют общие данные и код, а между приложениями полностью
Номера страниц
изолированы друг от друга.
Общие (разделяемые) ресурсы
Данные, код, устройства ввода/вывода, которые могут быть использованы несколькими процессами или потоками выполнения.
Критическая секция
Ресурсы характеризуются количеством, которое увеличивается когда к ним обращаются процессы поставщики ресурсов и уменьшается когда к ним идёт обращение процесса-потребителя ресурса. В
случае, если планировщик процессов произведёт переключение процесса в середине операции увеличения ресурса и начнётся операция по уменьшению ресурса, количество ресурсов может стать некорректным. Участки кода, на которых осуществляется доступ к разделяемым ресурсам при котором может возникнуть их некорректный учёт носит название критической секции.
Мьютекс
Mutex (от Mutable Exclusion) – исключение изменения – коммуникационный примитив,
предназначенный для возможности блокирования потока от выполнения в критической секции процесса. Мьютекс ставится в соответствие ресурсу, и процесс доступа к ресурсу устанавливает блокировку ресурса на время обращения. В конце обращения блокировка снимается, что разблокирует все другие процессы, ожидающие к нему доступа.
Условие (Condition variable)
Способ блокировки, когда процесс может заблокировать (перевести в ожидание выполнения какого- либо условия) сам себя. Для этого вызывается процесс переводит себя в состояние ожидания с помощью функции wait(); процесс, результат деятельности которого может снять блокировку с ожидающего процесса вызовом функции signal() пробуждает спящий процесс. Пример – ресурс временно отсутствует, следовательно процесс потребитель ресурса может находиться в ожидании.
Процесс производитель ресурса посылает сигнал пробуждения после увеличения количества ресурсов.
Синглетон
Глобальный статический объект (данные, код) приложения. Часто с помощью синглетона представляются различные глобальные менеджеры каких либо ресурсов, или сами ресурсы обладающие уникальностью. При многопоточном программировании, когда потоки разделяют общий контекст, синглетон позволяет обращаться к одному и тому же статическому объекту из разных мест приложения, не заботясь о синхронности его создания и инициализации.
Стражник
Guard – реализация мьютекса как автоматической переменной с ограниченным временем жизни.
Используется для повышения компактности описания критических секций, размер которых определяется размером соответствующего блока С++.
РЕАЛИЗАЦИЯ ПРИМИТИВОВ МНОГОПОТОЧНОГО ПРОГРАММИРОВАНИЯ
Файлы splib.h и splib.cpp содержат вариант простейшей библиотеки классов для многопоточного программирования. Для её использования необходимо скопировать их в каталог src проекта (или добавить в проект через automake manager) и включить файл интерфейса splib.h в те файлы, которые будут её использовать. Кроме этого, в параметрах проекта (параметры configure -> LDFLAGS)
необходимо добавить опцию -lpthread для подключения библиотеки POSIX Thread.
Листинг splib.h содержит определение классов библиотеки:
Номера страниц
/***************************************************************************
file: splib.h
Simple pthreads programming lib based on E.Surovegin ESL lib
***************************************************************************/
#ifndef __SPLIB_H_
#define __SPLIB_H_
#include
#include
#include
void splib_init(); // функция инициализации, должна быть вызвана перед использованием
//при старте программы
// класс Мьютекс
class Thread_Mutex
{
public:
Thread_Mutex();
Thread_Mutex(){ ::pthread_mutex_destroy(&mutex_); }
// блокировка
void acquire() const
{ ::pthread_mutex_lock(&mutex_);}
// освобождение
void release() const
{ ::pthread_mutex_unlock(&mutex_);}
private:
mutable pthread_mutex_t mutex_;
};
class Condition
{
public:
Condition();
Condition()
{
::pthread_mutex_destroy(&mutex_);
::pthread_cond_destroy(&cond_);
}
// Mutex-like behavior
// блокировка условной переменной на время её изменения
void acquire() const
{ ::pthread_mutex_lock(&mutex_); }
// оснятие блокировки
void release() const
{ ::pthread_mutex_unlock(&mutex_); }
// Event-like behavior
// вызывает переход процесса в ожидание события/сигнала
void wait() const
{ ::pthread_cond_wait(&cond_, &mutex_); }
// посылает сигнал пробуждения спящему процессу
void signal() const
{ ::pthread_cond_signal(&cond_); }
// посылает сигнал пробуждения всем процессам ожидающим данного условия
void broadcast() const
{ ::pthread_cond_broadcast(&cond_); }
// блокировка с ограничением по времени
/// Returns 0 - if ok, ETIMEDOUT or EINTR
int timed_wait(
const timespec* abstime) const
{
return ::pthread_cond_timedwait(&cond_, &mutex_, abstime);
}
private:
mutable pthread_cond_t cond_;
mutable pthread_mutex_t mutex_;
};
// Semaphores aren't actually part of the Pthreads standard.
Номера страниц
// В данной имплементации Семафор – примитив, который разрешает процессу продолжать
// выполнение если значение семафора (sem_) больше нуля, и приводит к блокировке
// если его значение равно нулю до момента когда его значение увеличится.
class Semaphore
{
public:
explicit Semaphore(size_t
/*maximum_count - ignored*/
=
0
, size_t initial_count =
0
,
const char
*
/*name - ignored*/
=
0
)
{
// Linux supports only thread semaphores (pshared == 0)
::sem_init(&sem_,
0
, initial_count);
}
Semaphore(){ ::sem_destroy(&sem_); }
void acquire() const
{ ::sem_wait(&sem_); }
void release() const
{ ::sem_post(&sem_); }
void wait() const
{ acquire(); }
private:
mutable sem_t sem_;
};
// Стражник – шаблон в качестве параметра которого может использоваться Мьютекс
// для создания автоматических объектов защищающих критические секции процессов
template <typename SYNC_OBJ>
class Guard
{
public:
Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
// блокировка
lock_.acquire();
}
// освобождение
Guard(){ lock_.release(); }
private:
const
SYNC_OBJ& lock_;
};
// Стражник с инверсным поведением
template <typename SYNC_OBJ>
class Reverse_Guard
{
public:
Reverse_Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
lock_.release();
}
Reverse_Guard(){ lock_.acquire(); }
private:
const
SYNC_OBJ& lock_;
};
// Синглетон – шаблон для создания глобальных объектов
// класс, определяющий функциональность объекта должен включать
// friend class Singleton
// для сокращения записи вызов можно осуществлять определив
// #define theClassName Singleton::instance()
// theClassName->method()
template<typename T>
class Singleton
{
Номера страниц
public:
// возвращает указатель на статический объект Т, если объекта не существует вызывает
// закрытый конструктор этого объекта через функцию create_instance()
static
T* instance()
{
if (!instance_)
create_instance();
return instance_;
}
private:
// конструктор не может быть вызван за пределами класса
Singleton() {}
Singleton() {}
static void create_instance();
static void close();
// статическая переменная, хранящая указатель на однократно созданный объект
static
T* instance_;
// мьютекс для блокировки объекта на время вызова конструктора класса
static thread_mutex lock_;
};
// template members
// исходная инициализация статического указателя на объект
template<typename T>
T* Singleton::instance_ =
0
;
template<typename T>
thread_mutex Singleton::lock_ = PTHREAD_MUTEX_INITIALIZER;
// объект создаётся лишь однократно
template<typename T>
void
Singleton::create_instance()
{
Guard guard(lock_);
if (!instance_)
{
instance_ = new T();
}
}
template<class T>
void
Singleton::close()
{
delete instance_;
instance_ =
0
;
}
// абстрактный класс PThread
class PThread
{
public:
void start(); // функция активации потока выполнения, вызывается из конструктора
// порождённого класса
void stop();
void join();
// Gracefull stop support
void signal_stop();
bool stop_requested() const
;
bool stopped() const
;
// Only for dynamic allocated instances
void destroy();
void delayed_destroy();
int priority() const
;
Номера страниц
void priority(
int
) const
;
unsigned id() const
{ return id_; }
const char
* name() const
{ return name_; }
// RT scheduler support
static int rt_max_priority();
static int rt_min_priority();
int rt_get_priority() const
;
void rt_set_priority(
int
);
protected:
// конструктор – защищённый, поэтому вызывается он только из порождённых классов
explicit PThread(
const char
* name =
""
);
virtual PThread();
// абстрактная функция, определяющая функциональность потока выполнения.
// должна быть реализована в порождённых классах
virtual void execute() =
0
;
private:
enum state { ts_waiting, ts_running, ts_stopping, ts_stopped };
static void
* __thread_proc(
void
*);
private:
Condition cond_;
pthread_t id_;
char name_[
20
]; volatile state state_;
int pipe_fd_[
2
];
bool auto_delete_;
};
#endif
// __SPLIB_H_
Задания и порядок их выполнения
1. Загрузите Linux и войдите под именем и паролем пользователя, выданного вам преподавателем.
2. Загрузите KDE, осуществив при необходимости необходимые настройки (язык, страна)
3. В домашнем каталоге создайте каталог $HOME/Design/lab1 4. Запустите Kdevelop и создайте проект test1 (проект типа Simple C++ application). Добавьте к проекту файлы splib.cpp и sblib.h. Попробуйте скомпилировать проект и исправить возможные ошибки.
5. Реализуйте поток выполнения, который в бесконечном цикле раз в секунду (sleep(1))
инкрементирует внутреннюю переменную и выводит в стандартный поток вывода (cout << ..) её
значение и имя потока. Начальное значение переменной должно задаваться в конструкторе класса. Создайте 2 объекта этого класса с именами thread1 и thread2 и различными начальными значениями внутренней переменной (в main()). После создания этих двух потоков вызовите функцию ожидания (sleep(20)), скомпилируйте и отладьте программу. Пронаблюдайте выводимые данные.
6. Test2: Измените класс, реализующий ваш поток так, чтобы внутренняя переменная стала статической. Пронаблюдайте как изменился вывод программы.
7. Test3: С помощью синглетона реализуйте глобальную переменную с нулевым начальным значением, с методами инкремента, декремента и чтения этой переменной. Реализуйте два класса для потоков, один из которых в бесконечном цикле инкрементирует эту переменную, а второй –
декрементирует. Напишите тест, в котором будут выдаваться сообщения если значение переменной будут больше 2 и меньше -2. Попробуйте понаблюдать за поведением программы.
Номера страниц
8. Test4: используя класс Condition реализуйте такое поведение глобального объекта, при котором переменная не может декрементироваться в случае если её значение меньше 0.
Номера страниц
Министерство образования Российской Федерации
НИЖЕГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ
УНИВЕРСИТЕТ
Кафедра «Информационные радиосистемы»
Реализация многопоточных приложений на С++
Методические указания к лабораторной работе № 1
по дисциплине «Дисциплина по выбору 1»
для студентов специальностей 200700 «Радиотехника»
Нижний Новгород 2006
Номера страниц
УДК 621.325.5-181.4
Реализация многопоточных приложений на С++: Метод. указания к лаб. работе № 1
по дисциплине «Сетевые информационные технологии» для студентов спец. 200700
/ НГТУ; Сост.: А.В.Миронов Н. Новгород, 2006 ??с.
Изложены начальные сведения о реализации многопоточных приложений с использованием объектно-ориентированного программирования. Сформулированы задания и порядок выполнения для лабораторной работы.
Редактор Э.А.Жирнова
Подп. к печ. . Формат ______. Бумага газетная. Печать офсетная. Печ.л. . Уч.-изд.л. . Тираж ____ экз. Заказ .
Нижегородский государственный технический университет.
Типография НГТУ. 603600, Н. Новгород, ул. Минина, 24.
© Нижегородский государственный технический университет, 2006
Номера страниц
Цель работы
Знакомство с примитивами многопотокового программирования. Получение навыков по объектно- ориентированному программированию многопоточных приложений.
Введение
Объектно-ориентированный подход к программированию позволяет достичь большей безопасности,
масштабируемости и в конечном итоге большей сложности приложений. Организация приложения как набора параллельно (аппаратно на нескольких процессорах или логически) выполняющихся процессов решающих небольшие задачи облегчает воплощение их функциональности. Тем не менее,
асинхронность процессов требует принятия специальных мер по обеспечению корректного обращения к разделяемым ресурсам. Эти ресурсы могут включать в себя различные коммуникационные примитивы, устройства ввода/вывода и т.п.
Появление многозадачной ОС UNIX и языка С сделало доступным многозадачное программирование для приложений через API ОС путём использования системного вызова fork(),
приводящего к разветвлению выполняющегося процесса на два. Недостатком подобной реализации является то, что порождаемые процессы получают копию всего контекста родительского процесса, и потом становятся изолированными от него и друг от друга, что с одной стороны повышает безопасность (независимость) отдельных процессов, а с другой приводит к повышенному использованию системных ресурсов и затрудняет взаимодействие между процессами.
Позже была разработана более экономная и быстродействующая реализация библиотеки для параллельного программирования под названием pthread или POSIX threads. Основным отличием потоков выполнения от процессов является то, что при разветвлении порождённые потоки разделяют один и тот же контекст родительского процесса, что экономит системные ресурсы но вместе с этим делает программирование менее безопасным (деятельность одного потока может нарушить деятельность другого).
Язык С++ и методология объектно-ориентированного программирования позволяет повысить безопасность разрабатываемых приложений за счёт возможности более строгого и формального определения областей видимости и типов данных и определения открытых и внутренних методов.
Тем самым, разработкой и тестированием классов для базовых примитивов параллельного программирования возможно повысить безопасность приложения, их использующего.
Базовые примитивы параллельного программирования
Процесс
Процессом называется последовательный код выполняющийся процессором. В однозадачной ОС
процесс тождественен программе в стадии выполнения. В многозадачной ОС одна и та же программа может быть запущена несколько раз что приводит к существованию нескольких выполняющихся логически параллельно процессов. Процесс может существовать в нескольких состояниях:
активен – когда процессор выполняет код процесса;
блокирован – когда процесс не может выполняться поскольку не соблюдаются условия для его выполнения (процесс читает данные из устройства ввода вывода, которое неготово)
готов для выполнения – когда процесс может выполняться, но процессор выполняет код другого процесса.
Решение о активизации процесса выполняется планировщиком ОС.
Поток выполнения
Поток выполнения, или облегчённый процесс, отличающийся от процесса меньшей изолированностью от других потоков выполнения. Процессы нескольких приложений практически одинаково изолированы друг от друга, в то время как потоки выполнения в пределах одного приложения (процесса ОС) разделяют общие данные и код, а между приложениями полностью
Номера страниц
Общие (разделяемые) ресурсы
Данные, код, устройства ввода/вывода, которые могут быть использованы несколькими процессами или потоками выполнения.
Критическая секция
Ресурсы характеризуются количеством, которое увеличивается когда к ним обращаются процессы поставщики ресурсов и уменьшается когда к ним идёт обращение процесса-потребителя ресурса. В
случае, если планировщик процессов произведёт переключение процесса в середине операции увеличения ресурса и начнётся операция по уменьшению ресурса, количество ресурсов может стать некорректным. Участки кода, на которых осуществляется доступ к разделяемым ресурсам при котором может возникнуть их некорректный учёт носит название критической секции.
Мьютекс
Mutex (от Mutable Exclusion) – исключение изменения – коммуникационный примитив,
предназначенный для возможности блокирования потока от выполнения в критической секции процесса. Мьютекс ставится в соответствие ресурсу, и процесс доступа к ресурсу устанавливает блокировку ресурса на время обращения. В конце обращения блокировка снимается, что разблокирует все другие процессы, ожидающие к нему доступа.
Условие (Condition variable)
Способ блокировки, когда процесс может заблокировать (перевести в ожидание выполнения какого- либо условия) сам себя. Для этого вызывается процесс переводит себя в состояние ожидания с помощью функции wait(); процесс, результат деятельности которого может снять блокировку с ожидающего процесса вызовом функции signal() пробуждает спящий процесс. Пример – ресурс временно отсутствует, следовательно процесс потребитель ресурса может находиться в ожидании.
Процесс производитель ресурса посылает сигнал пробуждения после увеличения количества ресурсов.
Синглетон
Глобальный статический объект (данные, код) приложения. Часто с помощью синглетона представляются различные глобальные менеджеры каких либо ресурсов, или сами ресурсы обладающие уникальностью. При многопоточном программировании, когда потоки разделяют общий контекст, синглетон позволяет обращаться к одному и тому же статическому объекту из разных мест приложения, не заботясь о синхронности его создания и инициализации.
Стражник
Guard – реализация мьютекса как автоматической переменной с ограниченным временем жизни.
Используется для повышения компактности описания критических секций, размер которых определяется размером соответствующего блока С++.
РЕАЛИЗАЦИЯ ПРИМИТИВОВ МНОГОПОТОЧНОГО ПРОГРАММИРОВАНИЯ
Файлы splib.h и splib.cpp содержат вариант простейшей библиотеки классов для многопоточного программирования. Для её использования необходимо скопировать их в каталог src проекта (или добавить в проект через automake manager) и включить файл интерфейса splib.h в те файлы, которые будут её использовать. Кроме этого, в параметрах проекта (параметры configure -> LDFLAGS)
необходимо добавить опцию -lpthread для подключения библиотеки POSIX Thread.
Листинг splib.h содержит определение классов библиотеки:
Номера страниц
/***************************************************************************
file: splib.h
Simple pthreads programming lib based on E.Surovegin ESL lib
***************************************************************************/
#ifndef __SPLIB_H_
#define __SPLIB_H_
#include
#include
#include
void splib_init(); // функция инициализации, должна быть вызвана перед использованием
//при старте программы
// класс Мьютекс
class Thread_Mutex
{
public:
Thread_Mutex();
Thread_Mutex(){ ::pthread_mutex_destroy(&mutex_); }
// блокировка
void acquire() const
{ ::pthread_mutex_lock(&mutex_);}
// освобождение
void release() const
{ ::pthread_mutex_unlock(&mutex_);}
private:
mutable pthread_mutex_t mutex_;
};
class Condition
{
public:
Condition();
Condition()
{
::pthread_mutex_destroy(&mutex_);
::pthread_cond_destroy(&cond_);
}
// Mutex-like behavior
// блокировка условной переменной на время её изменения
void acquire() const
{ ::pthread_mutex_lock(&mutex_); }
// оснятие блокировки
void release() const
{ ::pthread_mutex_unlock(&mutex_); }
// Event-like behavior
// вызывает переход процесса в ожидание события/сигнала
void wait() const
{ ::pthread_cond_wait(&cond_, &mutex_); }
// посылает сигнал пробуждения спящему процессу
void signal() const
{ ::pthread_cond_signal(&cond_); }
// посылает сигнал пробуждения всем процессам ожидающим данного условия
void broadcast() const
{ ::pthread_cond_broadcast(&cond_); }
// блокировка с ограничением по времени
/// Returns 0 - if ok, ETIMEDOUT or EINTR
int timed_wait(
const timespec* abstime) const
{
return ::pthread_cond_timedwait(&cond_, &mutex_, abstime);
}
private:
mutable pthread_cond_t cond_;
mutable pthread_mutex_t mutex_;
};
// Semaphores aren't actually part of the Pthreads standard.
Номера страниц
// В данной имплементации Семафор – примитив, который разрешает процессу продолжать
// выполнение если значение семафора (sem_) больше нуля, и приводит к блокировке
// если его значение равно нулю до момента когда его значение увеличится.
class Semaphore
{
public:
explicit Semaphore(size_t
/*maximum_count - ignored*/
=
0
, size_t initial_count =
0
,
const char
*
/*name - ignored*/
=
0
)
{
// Linux supports only thread semaphores (pshared == 0)
::sem_init(&sem_,
0
, initial_count);
}
Semaphore(){ ::sem_destroy(&sem_); }
void acquire() const
{ ::sem_wait(&sem_); }
void release() const
{ ::sem_post(&sem_); }
void wait() const
{ acquire(); }
private:
mutable sem_t sem_;
};
// Стражник – шаблон в качестве параметра которого может использоваться Мьютекс
// для создания автоматических объектов защищающих критические секции процессов
template <typename SYNC_OBJ>
class Guard
{
public:
Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
// блокировка
lock_.acquire();
}
// освобождение
Guard(){ lock_.release(); }
private:
const
SYNC_OBJ& lock_;
};
// Стражник с инверсным поведением
template <typename SYNC_OBJ>
class Reverse_Guard
{
public:
Reverse_Guard(
const
SYNC_OBJ& lock) : lock_(lock)
{
lock_.release();
}
Reverse_Guard(){ lock_.acquire(); }
private:
const
SYNC_OBJ& lock_;
};
// Синглетон – шаблон для создания глобальных объектов
// класс, определяющий функциональность объекта должен включать
// friend class Singleton
// для сокращения записи вызов можно осуществлять определив
// #define theClassName Singleton
// theClassName->method()
template<typename T>
class Singleton
{
Номера страниц
public:
// возвращает указатель на статический объект Т, если объекта не существует вызывает
// закрытый конструктор этого объекта через функцию create_instance()
static
T* instance()
{
if (!instance_)
create_instance();
return instance_;
}
private:
// конструктор не может быть вызван за пределами класса
Singleton() {}
Singleton() {}
static void create_instance();
static void close();
// статическая переменная, хранящая указатель на однократно созданный объект
static
T* instance_;
// мьютекс для блокировки объекта на время вызова конструктора класса
static thread_mutex lock_;
};
// template members
// исходная инициализация статического указателя на объект
template<typename T>
T* Singleton
0
;
template<typename T>
thread_mutex Singleton
// объект создаётся лишь однократно
template<typename T>
void
Singleton
{
Guard
if (!instance_)
{
instance_ = new T();
}
}
template<class T>
void
Singleton
{
delete instance_;
instance_ =
0
;
}
// абстрактный класс PThread
class PThread
{
public:
void start(); // функция активации потока выполнения, вызывается из конструктора
// порождённого класса
void stop();
void join();
// Gracefull stop support
void signal_stop();
bool stop_requested() const
;
bool stopped() const
;
// Only for dynamic allocated instances
void destroy();
void delayed_destroy();
int priority() const
;
Номера страниц
int
) const
;
unsigned id() const
{ return id_; }
const char
* name() const
{ return name_; }
// RT scheduler support
static int rt_max_priority();
static int rt_min_priority();
int rt_get_priority() const
;
void rt_set_priority(
int
);
protected:
// конструктор – защищённый, поэтому вызывается он только из порождённых классов
explicit PThread(
const char
* name =
""
);
virtual PThread();
// абстрактная функция, определяющая функциональность потока выполнения.
// должна быть реализована в порождённых классах
virtual void execute() =
0
;
private:
enum state { ts_waiting, ts_running, ts_stopping, ts_stopped };
static void
* __thread_proc(
void
*);
private:
Condition cond_;
pthread_t id_;
char name_[
20
]; volatile state state_;
int pipe_fd_[
2
];
bool auto_delete_;
};
#endif
// __SPLIB_H_
Задания и порядок их выполнения
1. Загрузите Linux и войдите под именем и паролем пользователя, выданного вам преподавателем.
2. Загрузите KDE, осуществив при необходимости необходимые настройки (язык, страна)
3. В домашнем каталоге создайте каталог $HOME/Design/lab1 4. Запустите Kdevelop и создайте проект test1 (проект типа Simple C++ application). Добавьте к проекту файлы splib.cpp и sblib.h. Попробуйте скомпилировать проект и исправить возможные ошибки.
5. Реализуйте поток выполнения, который в бесконечном цикле раз в секунду (sleep(1))
инкрементирует внутреннюю переменную и выводит в стандартный поток вывода (cout << ..) её
значение и имя потока. Начальное значение переменной должно задаваться в конструкторе класса. Создайте 2 объекта этого класса с именами thread1 и thread2 и различными начальными значениями внутренней переменной (в main()). После создания этих двух потоков вызовите функцию ожидания (sleep(20)), скомпилируйте и отладьте программу. Пронаблюдайте выводимые данные.
6. Test2: Измените класс, реализующий ваш поток так, чтобы внутренняя переменная стала статической. Пронаблюдайте как изменился вывод программы.
7. Test3: С помощью синглетона реализуйте глобальную переменную с нулевым начальным значением, с методами инкремента, декремента и чтения этой переменной. Реализуйте два класса для потоков, один из которых в бесконечном цикле инкрементирует эту переменную, а второй –
декрементирует. Напишите тест, в котором будут выдаваться сообщения если значение переменной будут больше 2 и меньше -2. Попробуйте понаблюдать за поведением программы.
Номера страниц
8. Test4: используя класс Condition реализуйте такое поведение глобального объекта, при котором переменная не может декрементироваться в случае если её значение меньше 0.
Номера страниц