10. Лекция: Потоки (threads) и многопоточное выполнение программ



Скачать 327.6 Kb.
Дата18.11.2016
Размер327.6 Kb.
Просмотров723
Скачиваний0
ТипРеферат
10. Лекция: Потоки (threads) и многопоточное

выполнение программ (multi-threading)

В лекции рассматриваются понятие потока (thread) и многопоточное выполнение (multi-threading); модели многопоточности; пользовательские потоки и потоки ядра; потоки в Solaris, Linux, POSIX, Windows 2000, Java.



Содержание

  • Введение

  1. Однопоточные и многопоточные процессы

  2. История многопоточности

  3. Пользовательские потоки и потоки ядра

  4. Проблемы многопоточности

  5. Потоки POSIX (Pthreads)

  6. Потоки и процессы в Solaris

  7. Потоки в Windows 2000

  8. Потоки в Linux

  9. Потоки в Java

  10. Ключевые термины

  11. Краткие итоги

  12. Набор для практики

o

Вопросы

o

Упражнения

o

Темы для курсовых работ, рефератов, эссе

Введение


Многопоточность (multi-threading) – одна из наиболее интересных и актуальных тем в данном курсе и, по-видимому, в области ИТ вообще. Актуальность данной темы особенно велика, в связи с широким распространением многоядерных процессоров. В лекции рассмотрены следующие вопросы:

- Исторический обзор многопоточности

- Модели многопоточного исполнения

- Проблемы, связанные с потоками

- Потоки в POSIX (Pthreads)

- Потоки в Solaris 2

- Потоки в Windows 2000/XP

- Потоки в Linux

- Потоки в Java и .NET.

Однопоточные и многопоточные процессы


К сожалению, до сих пор мышление многих программистов при разработке программ остается чисто последовательным. Не учитываются широкие возможности параллелизма, в частности, многопоточности. Последовательный (однопоточный) процесс – это процесс, который имеет только один поток управления (control flow), характеризующийся изменением его счетчика команд. Поток (thread) – это запускаемый из некоторого процесса особого рода параллельный процесс, выполняемый в том же адресном пространстве, что и процесс-родитель. Схема организации однопоточного и многопоточного процессов изображена на рис. 10.1.



Рис. 10.1. Однопоточный и многопоточный процессы.

Как видно из схемы, однопоточный процесс использует, как обычно, код, данные в основной памяти и файлы, с которыми он работает. Процесс также использует определенные значения регистров и стек, на котором исполняются его процедуры. Многопоточный процесс организован несколько сложнее. Он имеет несколько параллельных потоков, для каждого из которых ОС создает свой стек и хранит свои собственные значения регистров. Потоки работают в общей основной памяти и используют то же адресное пространство, что и процесс-родитель, а также разделяют код процесса и файлы.

Многопоточность имеет большие преимущества:


  • Увеличение скорости (по сравнению с использованием обычных процессов).

Многопоточность основана на использовании облегченных процессов (lightweight processes), работающих в общем пространстве виртуальной памяти. Благодаря многопоточности, не возникает больше неэффективных ситуаций, типичных для классической системе UNIX, в которой каждая команда shell (даже команда вывода содержимого текущей директории ls исполнялась как отдельный процесс, причем в своем собственном адресном пространстве. В противоположность облегченным процессам, обычные процессы (имеющие собственное адресное пространство) часть называют тяжеловесными (heavyweight).

  • Использование общих ресурсов. Потоки одного процесса используют общую память и файлы.

  • Экономия. Благодаря многопоточности, достигается значительная экономия памяти, по причинам, объясненным выше. Также достигается и экономия времени, так как переключение контекста на облегченный процесс, для которого требуется только сменить стек и восстановить значения регистров, значительно быстрее, чем на обычный процесс (см. "Методы взаимодействия процессов ").

Использование мультипроцессорных архитектур. Это особенно важно в настоящее время, в период широкого использования многоядерных гибридных и многопроцессорных систем. Именно многопоточность программ, основанная на многоядерности процессора, дает возможность, наконец, почувствовать реальные преимущества параллельного выполнения.

История многопоточности


Концепция многопоточности начала складываться, по-видимому, с 1980-х гг. в системе

UNIX и ее диалектах. Наиболее развита многопоточность была в диалекте UNIX фирмы AT&T, на основе которого, как уже отмечалось в общем историческом обзоре, была разработана система Solaris. Все это отразилось и в стандарте POSIX, в который вошла и многопоточность, наряду с другими базовыми возможностями UNIX.

Далее, в середине 1990-х гг. была выпущена ОС Windows NT, в которую была также включена многопоточность.

Однако в разных операционных системах API для многопоточности существенно отличались. Поэтому многопоточные программы, даже написанные на языках высокого уровня, оказались не переносимыми с одной платформы на другую, что, разумеется, создавало большие неудобства.

По-видимому, именно по причине различий в спецификациях и реализациях многопоточности в различных системах профессор Бьярн Страуструп не включил многопоточность в созданный им язык C++, ставший столь популярным, и его базовый набор библиотек. Программисты на языке C++ были вынуждены по-прежнему использовать многопоточность на уровне системных вызовов и библиотек конкретных операционных систем.

Важный шаг вперед сделали авторы языка Java и Java-технологии, первая версия реализации которых была выпущена в 1995 г. Именно в Java впервые многопоточность была реализована на уровне конструкций языка и базовых библиотек. В частности, в Java введен класс Thread, представляющий поток, и операции над ним в виде специальных методов и конструкций языка.

Платформа .NET, появившаяся в 2000 г., предложила свой механизм многопоточности, который фактически является развитием идей Java.

Различие подходов к многопоточности в разных ОС и на разных платформах разработки программ сохраняется и до настоящего времени, что приходится постоянно учитывать разработчикам. Для прикладных программ мы рекомендуем реализовывать многопоточность на платформе Java или .NET, что наиболее удобно и позволяет использовать высокоуровневые понятия и конструкции. Однако в нашем курсе, посвященном операционным системам, мы, естественно, больше внимания уделяем системным вопросам многопоточности и ее реализации в операционных системах.




Пользовательские потоки и потоки ядра


Модели многопоточности. Реализация многопоточности в ОС, как и многих других возможностей, имеет несколько уровней абстракции. Самый высокий из них – пользовательский уровень. С точки зрения пользователя и его программ, управление потоками реализовано через библиотеку потоков пользовательского уровня (user threads). Подробнее конкретные операции над пользовательскими потоками будут рассмотрены немного позже. Пока отметим лишь, что существует несколько моделей потоков пользовательского уровня, среди которых:



POSIX Pthreadsпотоки, специфицированные стандартом POSIX и используемые в POSIX-приложениях (рассмотрены позже в данной лекции);



Mac C-threads – пользовательские потоки в системе MacOS;

Solaris threads – пользовательские потоки в ОС Solaris (рассмотрены позже в данной лекции).

Низкоуровневые потоки, в которые отображаются пользовательские потоки, называются потоками ядра (kernel threads). Они поддержаны и используются на уровне ядра операционной системы. Как и подходы к пользовательским потокам, подходы к архитектуре и реализации системных потоков и к отображению пользовательских потоков в системные в разных ОС различны .Например, собственные модели потоков ядра со своей спецификой реализованы в следующих ОС:



  • Windows 95/98/NT/2000/XP/2003/2008/7;

  • Solaris;

  • Tru64 UNIX;

  • BeOS;

  • Linux.

Существуют различные модели многопоточности – способы отображения пользовательских потоков в потоки ядра. Теоретически возможны (и на практике реализованы) следующие модели многопоточности:

  • Модель много / один (many-to-one) – отображение нескольких пользовательских потоков в один и тот же поток ядра. Используется в операционных системах, не поддерживающих множественные системные потоки (например, с целью экономии памяти). Данная модель изображена на рис. 10.2.



Рис. 10.2. Схема модели многопоточности "много / один".

  • Модель один / один (one-to-one) – взаимно-однозначное отображение каждого пользовательского потока в определенный поток ядра. Примеры ОС, использующих данную модель, - Windows 95/98/NT/2000/XP/2003/2008/7; OS/2. Данная модель изображена на рис. 10.3.



Рис. 10.3. Схема модели многопоточности "один / один".

  • Модель много / много (many-to-many) – модель, допускающая отображение нескольких пользовательских потоков в несколько системных потоков. Такая модель позволяет ОС создавать большое число системных потоков. Характерным примером ОС, использующей подобную модель, является ОС Solaris, а также Windows NT / 2000 / XP / 2003 / 2008 / 7 с пакетом ThreadFiber. Данная модель изображена на рис. 10.4.



Рис. 10.4. Схема модели многопоточности "много / много".

Проблемы многопоточности


Многопоточность – весьма сложная, еще не полностью изученная и, тем более, не полностью формализованная область, в которой имеется много интересных проблем. Рассмотрим некоторые из них.

Семантика системных вызовов fork() и exec(). Как уже отмечалось, в классической ОС UNIX системный вызов fork создает новый "тяжеловесный" процесс со своим адресным пространством, что значительно "дороже", чем создание потока. Однако, с целью поддержания совместимости программ снизу вверх, приходится сохранять эту семантику, а многопоточность вводить с помощью новых системных вызовов.

Прекращение потоков. Важной проблемой является проблема прекращения потоков: например, если родительский поток прекращается, то должен ли при этом прекращаться дочерний поток? Если прекращается стандартный процесс, создавший несколько потоков, то должны ли прекращаться все его потоки? Ответы на эти вопросы в разных ОС неоднозначны.

Обработка сигналов. Сигналы в UNIX – низкоуровневый механизм обработки ошибочных ситуаций. Примеры сигналов: SIGSEGV - нарушение сегментации (обращение по неверному адресу, чаще всего по нулевому); SIGKILLсигнал процессу о выполнении команды kill его уничтожения. Пользователь может определить свою процедуру-обработчик сигнала системным вызовом signal. Проблема в следующем: как распространяются сигналы в многопоточных программах и каким потоком они должны обрабатываться? В большинстве случаев этот вопрос решается следующим образом: сигнал обрабатывается потоком, в котором он сгенерирован, и влияет на исполнение только этого потока. В более современных ОС (например, Windows 2000 и более поздних версиях Windows), основанных на объектноориентированной методологии, концепция сигнала заменена более высокоуровневой концепцией исключения (exception). Исключение распространяется по стеку потока в порядке, обратном порядку вызовов методов, и обрабатывается первым из них, в котором система находит подходящий обработчик. Аналогичная схема обработки исключений реализована в Java и в .NET.

Группы потоков. В сложных задачах, например, задачах моделирования, при числе разнородных потоков, возникает потребность в их структурировании и помощью концепции группы потоков – совокупности потоков, имеющей свое собственное имя, над потоками которой определены групповые операции. Наиболее удачно, с нашей точки зрения, группы потоков реализованы в Java (с помощью класса ThreadGroup). Следует отметить также эффективную реализацию пулов потоков (ThreadPool) в .NET.

Локальные данные потока (thread-local storage - TLS) – данные, принадлежащие только определенному потоку и используемые только этим потоком. Необходимость в таких данных очевидна, так как многопоточность – весьма важный метод распараллеливания решения большой задачи, при котором каждый поток работает над решением порученной ему части. Все современные операционные системы и платформы разработки программ поддерживают концепцию локальных данных потока.

Синхронизация потоков. Поскольку потоки, как и процессы могут использовать общие ресурсы и реагировать на общие события, необходимы средства их синхронизации. Эти средства подробно рассмотрены позже в данном курсе.

Тупики (deadlocks) и их предотвращение. Как и процессы, потоки могут взаимно блокировать друг друга (т.е. может создаться ситуация deadlock), при их неаккуратном программировании. Меры по борьбе с тупиками подробно рассмотрены позже в данном курсе.

Потоки POSIX (Pthreads)


В качестве конкретной модели многопоточности рассмотрим потоки POSIX (напомним, что данная аббревиатура расшифровывается как Portable Operating Systems Interface of uniX kind – стандарты для переносимых ОС типа UNIX). Многопоточность в POSIX специфицирована стандартом IEEE 1003.1c, который описывает API для создания и синхронизации потоков. Отметим, что POSIX-стандарт API определяет лишь требуемое поведение библиотеки потоков. Реализация потоков оставляется на усмотрение авторов конкретной POSIX-совместимой библиотеки. POSIX-потоки распространены в ОС типа UNIX, а также поддержаны, с целью совместимости программ, во многих других ОС, например, Solaris и Windows NT.

Стандарт POSIX определяет два основных типа данных для потоков: pthread_t – дескриптор потока; pthread_attr_t – набор атрибутов потока.

Стандарт POSIX специфицирует следующий набор функций для управления потоками:

- pthread_create(): создание потока

- pthread_exit(): завершение потока (должна вызываться функцией потока при завершении)

- pthread_cancel(): отмена потока

- pthread_join(): заблокировать выполнение потока до прекращения другого потока, указанного в вызове функции

- pthread_detach(): освободить ресурсы занимаемые потоком (если поток выполняется, то освобождение ресурсов произойдѐт после его завершения)

- pthread_attr_init(): инициализировать структуру атрибутов потока

- pthread_attr_setdetachstate(): указать системе, что после завершения потока она может автоматически освободить ресурсы, занимаемые потоком

- pthread_attr_destroy(): освободить память от структуры атрибутов потока (уничтожить дескриптор).

Имеются следующие примитивы синхронизации POSIX-потоков с помощью мюьтексов



(mutexes) – аналогов семафоров – и условных переменных (conditional variables) – оба эти типа объектов для синхронизации подробно рассмотрены позже в данном курсе:

  • pthread_mutex_init() – создание мюьтекса;

  • pthread_mutex_destroy() – уничтожение мьютекса;

  • pthread_mutex_lock() – закрытие мьютекса;

  • pthread_mutex_trylock() – пробное закрытие мьютекса (если он уже закрыт, вызов игнорируется, и поток не блокируется);

  • pthread_mutex_unlock() – открытие мьютекса;

  • pthread_cond_init() – создание условной переменной;

  • pthread_cond_signal() – разблокировка условной переменной; - pthread_cond_wait() – ожидание по условной переменной.

Рассмотрим пример использования POSIX-потоков на языке Си.

#include

#include

#include

#include
static void wait_thread(void)

{

time_t start_time = time(NULL); while (time(NULL) == start_time)



{

// никаких действий, кроме занятия процессора на время до 1 с.

}

} static void *thread_func(void *vptr_args)



{ int i;

for (i = 0; i < 20; i++) { fputs(" b\n", stderr); wait_thread();

}

return NULL;



}

int main(void) { int i; pthread_t thread;

if (pthread_create(&thread, NULL, thread_func, NULL) != 0) { return EXIT_FAILURE;

}

for (i = 0; i < 20; i++) { puts("a"); wait_thread();



}

if (pthread_join(thread, NULL) != 0) {

return EXIT_FAILURE;

}


return EXIT_SUCCESS; }

Пример иллюстрирует параллельное выполнение основного потока, выдающего в стандартный вывод последовательность букв "a", и дочернего потока, выдающего в стандартный поток ошибок (stderr) последовательность букв "b". Обратите внимание на особенности создания потока (pthread_create), указания его тела (исполняемой процедуры потока thread_func) и ожидания завершения дочернего потока (pthread_join).


Потоки и процессы в Solaris


В ОС Solaris, как уже было отмечено, используется модель потоков много / много. Кроме того, в системе используется также уже известное нам понятие облегченный процесс (lightweight process промежуточное между концепцией пользовательского потока и системного потока. Таким образом, в ОС Solaris каждый пользовательский поток отображается в свой облегченный процесс, который, в свою очередь, отображается в поток ядра; последний может исполняться на любом процессоре (или ядре процессора) компьютерной системы. Схема организации потоков в Solaris изображена на рис. 10.5.



Рис. 10.5. Потоки в Solaris.

На рис. 10.6 изображена схема организации процесса в ОС Solaris.





Рис. 10.6. Процессы в Solaris.

На схеме видно, что каждый процесс содержит, кроме стандартной информации блока управления процессом, также список всех своих облегченных процессов для управления ими.


Потоки в Windows 2000


Как уже отмечалось, в системе Windows реализована модель многопоточности "один / один". Каждый поток содержит:

идентификатор потока (thread id); набор регистров отдельные стеки для пользовательских и системных процедур; область памяти для локальных данных потока (thread-local storage – TLS).


Потоки в Linux


В системе Linux потоки называются tasks (задачами), а не threads. Поток создается системным вызовом clone (). Данный системный вызов позволяет дочерней задаче использовать общее адресное пространство с родительской задачей (процессом).

Потоки в Java


Как уже отмечалось, Java – первая платформа для разработки программ, в которой многопоточность поддержана на уровне языка и базовых библиотек. Потоки в Java могут быть созданы следующими способами:

Как расширения класса Thread

Как классы, реализующие интерфейс Runnable, который содержит единственный метод run – исполняемое тело потока.

Потоки в Java управляются JVM. Возможно создание групп потоков и иерархии таких групп.

Возможные состояния потоков в Java изображены на рис. 10.7. Подобно потокам в ОС, поток в Java создается и находится в состоянии новый, затем – выполняемый; при вызове методов типа wait, sleep и др. поток переходит в состояние ожидания; при завершении метода run поток завершается.



Рис. 10.7. Состояния потоков в Java.

Ключевые термины


Mac C-threads – пользовательские потоки в системе MacOS.

POSIX Pthreads – потоки, специфицированные стандартом POSIX и используемые в POSIX-приложениях.

Solaris threads – пользовательские потоки в ОС Solaris.

Thread – класс, представляющий поток, в языке Java.

Атрибуты потока – совокупность атрибутов POSIX-потока, описываемая типом pthread_attr_t.

Группа потоков (thread group) – совокупность потоков, имеющей свое собственное имя, над потоками которой определены групповые операции.



Дескриптор потока – ссылка на POSIX-поток, описываемая типом pthread_t.

Задача (task) – название потока в Linux.

Исключение (exception) – высокоуровневый механизм обработки ошибочных ситуаций в объектно-ориентированных языках и операционных системах.

Локальные данные потока (thread-local storage - TLS) – данные, принадлежащие только определенному потоку и используемые только этим потоком.

Модель многопоточности способ отображения пользовательских потоков в потоки ядра.

Модель много / много - модель многопоточности, при которой различные пользовательские потоки могут быть отображены в различные потоки ядра.

Модель много / один - модель многопоточности, при которой несколько пользовательских потоков могут быть отображены в один поток ядра.

Модель один / один модель многопоточности, при которой каждый пользовательский поток отображается в один определенный поток ядра.

Мьютекс (mutex) – аналог семафоров, обеспечивающий взаимное исключение, используемый в операционных системах.

Облегченный процесс (lightweight process) – процесс, работающий в общем пространстве виртуальной памяти с процессом-родителем.

Поток (thread) – запускаемый из какого-либо процесса более эффективный вариант параллельного процесса, выполняемый в том же адресном пространстве, что и процессродитель.

Поток пользовательского уровня (user thread) - высокоуровневый поток, операции над которым включены в интерфейс пользователя ОС.

Поток ядра (kernel thread) - низкоуровневый системный поток, поддержанный и использующийся на уровне ядра операционной системы; используется для реализации потоков пользовательского уровня.

Пул потоков (ThreadPool) – эффективный механизм структурирования потоков в группы в .NET.

Сигналы (в UNIX) – низкоуровневый механизм обработки ошибочных ситуаций.

"Тяжеловесный" (heavyweight) процесс – название классического процесса, работающего в собственном адресном пространстве, в противоположность облегченному процессу.

Условная переменная (conditional variable) - синхронизирующий объект, используемый в операционных системах, с операциями wait и signal.

Краткие итоги


Многопоточность (multi-threading) – современное направление программирования, особенно актуальное в связи с широким распространением параллельных компьютерных архитектур. Поток – особый вид процесса, выполняемый в общем адресном пространстве с процессом-родителем. Поток характеризуется своим стеком, потоком управления и значениями регистров. Облегченный процесс (lightweight process) – механизм, с помощью которого реализуются потоки в ОС.

Впервые понятие процесса, близкое современной концепции потока, было реализовано в системе "Эльбрус" в конце 1970-х гг. Многопоточность появилась в UNIX, затем – в Solaris и Windows NT. В различных ОС архитектуры библиотек поддержки многопоточности различаются. В Java-технологии, а вслед за ней – в .NET, впервые многопоточность была реализована на уровне языка и базовых библиотек.

Архитектура потоков – многоуровневая: потоки пользовательского уровня реализуются с помощью системных потоков (потоков ядра). Существуют различные модели многопоточности (способы отображения пользовательских потоков в системные) – один-один, один-много, много-один.

Многопоточность ставит ряд интересных проблем: семантика системных вызовов fork и exec; прекращение потоков; обработка сигналов; структуризация потоков в группы; поддержка локальных данных потока (TLS); синхронизация потоков; тупики (взаимная блокировка потоков) и их предотвращение.

POSIX threads (Pthreads) - стандартизация API для поддержки многопоточности для операционных систем типа UNIX. Поток характеризуется своим дескриптором и атрибутами. Для синхронизации потоков используются мьютексы и условные переменные.

Потоки в ОС Solaris отличаются тем, что явно присутствует понятие облегченного процесса, наряду с понятиями пользовательского и системного потоков. Каждый традиционный процесс хранит список созданных в нем облегченных процессов Используется модель многопоточности "много-много".

В Windows 2000 используется модель многопоточности "один-один". Каждый поток содержит свой номер, набор регистров, отдельные стеки для пользовательских и системных процедур, локальную память потока (TLS).

В Linux потоки называются задачами (tasks) и создаются системным вызовом clone.



Потоки в Java поддержаны на уровне языка и базовых библиотек. Представляются объектами класса Thread и его подклассов. Управляются виртуальной машиной Java. Возможно создание групп потоков. Состояния потоков аналогичны используемым в ОС.

Набор для практики

Вопросы


  1. Что такое поток?

  2. Чем отличаются однопоточные и многопоточные процессы?

  3. В чем преимущества многопоточности?

  4. В какой системе впервые было реализовано понятие процесса, близкое современному понятию потока?

  5. В какой ОС многопоточность впервые появилась "официально"?

  6. На какой платформе разработки программ многопоточность впервые была реализована на уровне языка и базовых библиотек?

  7. Что такое пользовательские потоки?

  8. Что такое системные потоки и чем они отличаются от пользовательских?

  9. Какие существуют модели многопоточности?

  10. В чем суть модели много/много?

  11. В чем суть модели много/один?

  12. В чем суть модели один/один?

  13. Каковы проблемы многопоточности?

  14. Что такое сигнал и как он обрабатывается в многопоточной программе?

  15. Что такое исключение и как оно обрабатывается в многопоточной программе?

  16. Что такое группа потоков?

  17. Что такое пул потоков?

  18. Что такое локальная память (данные) потока?

  19. Что такое Pthread?

  20. Какими типами данных описывается поток в POSIX?

  21. Какие средства синхронизации потоков используются в POSIX?

  22. В чем отличие потоков в Solaris от потоков в других системах?

  23. Что такое облегченный процесс?

  24. В чем отличие потоков в Windows 2000?

  25. В чем отличие потоков в Linux?

  26. Как представляются потоки в Java, каковы операции над ними и состояния потоков?

Упражнения


  1. Проанализируйте и опишите преимущества, недостатки и проблемы многопоточности.

  2. Реализуйте модель многопоточности один/один.

  3. Реализуйте модель многопоточности много/один.

  4. Реализуйте модель многопоточности много/много.

  5. Напишите на Си программу параллельного умножения матриц с использованием POSIX-потоков.

  6. Напишите на Си программу параллельного умножения матриц с использованием потоков Solaris.

  7. Напишите на Java программу параллельного умножения матриц с использованием Java-потоков.

Темы для курсовых работ, рефератов, эссе


  1. История концепции потока и многопоточности в операционных системах и языках программирования (реферат).

  2. Обзор многопоточности в UNIX, Linux, Solaris (реферат).

  3. Обзор многопоточности в POSIX (реферат).

  4. Обзор многопоточности в MacOS (реферат).

  5. Обзор многопоточности в Java (реферат).

  6. Обзор многопоточности в .NET (реферат).

  7. Обзор многопоточности в Windows 2000 / XP / 2003 / 2008 / 7.

  8. Разработка на Си программы параллельного умножения матриц с использованием POSIX-потоков.

  9. Разработка на Си программы параллельного умножения матриц с использованием потоков Solaris.

  10. Разработка на Java программы параллельного умножения матриц с использованием Java-потоков.


Поделитесь с Вашими друзьями:


База данных защищена авторским правом ©nethash.ru 2017
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал