Лекция 04 - chrislvt/OS GitHub Wiki
00:22
На прошлой лекции мы с вами начали рассматривать потоки при этом к потокам мы пришли в результате следующего суждения - переключение контекста.
При этом различается переключение:
- аппаратного контекста
- полного контекста.
00:59
Переключение аппаратного контекста
Переключение аппаратного контекста это:
- сохранение содержимого регистров процессора
- переход на выполнение какой-то другой работы
- восстановление содержимого регистров процессора
Т.е. при таком переключении системе не нужна информация о ресурсах процесса т.к. переключается только аппаратный контекст.
01:54
Переключение полного контекста
Но, если процессор переключается с выполнения одного процесса на выполнение другого процесса, то происходит переключение полного контекста, который включает в себя:
- аппаратный контекст
- информацию о выделенных процессу ресурсах
Ну я не уверена что буду до конца вами понятной, но в процессе нашего курса, я надеюсь, те студенты, которые пытаюстя глубого разобраться, из анализа структур того же линукс найдут соответствующую информацию.
Основной ресурс, который выделяется процессу, это оперативная память.
Забегая вперед, скажу, что в современных системах используется управление памятью страницами по запросу, и для этого в системе имеются соответствующие таблицы - таблицы страниц. Ну они называются по-разному в разных системах, ну, можно сказать так, что, например линукс оперирует рядом структур, которые описывают выделенную процессу память.
Кроме памяти, процессу могут выделены другие ресурсы, например файлы открытые процессом. Каждый процесс имеет собственную таблицу открытых файлов.
Т.е. ну вы в своих лабораторных работах по программированию, выполняя задания, создавали файлы, с точки зрения системы это называется открытые файлы. Каждый процесс имеет собственную таблицу открытых файлов. Это в общем-то тоже ресурс.
Кроме этого процесс может запрашивать массу других ресурсов у системы. Информация о всех запрошенных процессом ресурсах должна присутствовать в дескрипторе процесса в виде отдельных таблиц (указателей на отдельные таблицы). Это отдельные указатели, которые указывают на структуры, описывающие соответствующие ресурсы.
Т.е. переключение процесса связано с восстановлением в системе этой информации (этой == информации о ресурсах, выделенных процессу). Фактически получается, что структура процесса (дескриптор) переходит из одной очереди в другую, а дальше система начинает оперировать этой структурой - это затратное действие. Кроме того, регистры должны быть восстановлены. Содержимое регистров необходимо сохранить - это крайне затратное действие. При этом, переключение процессов, как вы любите говорить, выполняется в системе постоянно (это слово ее триггерит, лучше его избегать).
Процесс расходует свой квант и происходит переключение на выполнение другого процесса, либо процессу выделяется следующий квант, если его приоритет наивысший. Для того чтобы сократить накладные расходы на переключение полного контекста, были введены потоки (thread, тред, сред). Потоки глобально бывают двух типов:
- потоки режима пользователя
- потоки режима ядра
Процесс расходует свой квант и происходит переключение на выполнение другого процесса. Ну это немножечко не соответствует работе персонального компьютера, здесь процессу выделяется следующий квант, если так сказать, его приоритет наивысший, так сказать, поскольку за компьютером сидит один единственный пользователь. Да, вот если вы работаете с программой и, так сказать, у вас окно открытое, значит в итоге выделяются следующие кванты, но это не значит, что в системе не выполняется никакая другая работа. Ну, скажем, мы пока не можем посмотреть это в линукс, но в виндус вы это наверняка видели в таск менежере, какое большое количество процессов одновременно выполняется в вашей системе и вы можете между ними не переключаться, но они все равно требуют внимания системы. Ну, что значит "требуют внимания системы", - это процессорное время. Для того чтобы сократить накладные расходы на переключение контекста, были введены потоки. Т.е. стремление к сокращению накладных расходов по переключению полного контектса привело к идее и затем к ее воплощению в виде потоков (тредов). Ну в мире средов не все однозначно. Начнем с того, что потоки глобально бывают двух типов:
- потоки режима пользователя
- потоки режима ядра
08:42
Потоки режима пользователя.
Ну значит наиболее такое яркое изображение потоков в виде таких червяков.
Потоки режима пользователя существуют только в пользовательском режиме.
Определение потока давала на прошлом занятии. Поток - непрерывная часть кода, которая может выполняться с другими частями кода.
Для управления потоками режима пользователей необходима специальная библиотека потоков, которая предоставляет соответствующие функции для работы с потоками. Выполнение потоков может планироваться на уровне пользователя, переключаться, потокам могут предоставляться разделяемые ресурсы в режиме пользователя, об этих потоках ядро ничего не знает. Ядро знает только о процессе.
Т.е. вот эта программа (тык тык по доске) это потоки выделенные в этой программе, части кода, которые могут выполняться паралельно и этими потоками управляют с помощью соответствующей библиотеки. Ну вы можете посмотреть какие библиотеки существуют в плане любопытства, используются, но самое важное для нас то, что ядро об этих потоках ничего не знает.
Все прекрасно до тех пор, пока выполняемый поток не запросил ресурс, который может быть выделен только ядром, например, ввод вывод. Ни одна ос не позволяет приложению напрямую обратиться к устройствам ввода/вывода. Иначе такую систему невозможно было бы защитить.
Для того чтобы обратиться к устройству ввода/вывода выполняется системный вызов, происходит переход в режим ядра, соответственно все потоки блокируются, поскольку блокируется процесс. Затем возврат из состояния блокировки выполняется так же, как для обычного процесса: получив прерывание от внешнего устройства, система, обработав это прерывание, должна запрошенные данные вернуть процессу; в процессе эти данные возвращаются потоку, который запросил ввод вывод.
Ну все потоки при этом блокируются, поскольку, как я уже сказала, ядро о сущестовавании этих потоков ничего не знает. Теперь все хорошо до тех пор пока нет запроса к ресурсу, который может быть выделен только ядром, не выполняется системный вызов.
14:50
Потоки режима ядра
Вот это потоки режима пользователя, второй тип потоков - потоки режима ядра.
Программа, условно, делится на потоки, каждому выделенному в программе потоку соответствует поток ядра. Это просто иллюстрация(!) идеи. Ключевой момент, влияющий на эффективность работы системы: владельцем ресурсов остается процесс. Но процессорное время получают потоки. Поток владеет счетчиком команд. Если в программа никакие потоки не создаются, то для общности управления в процессе создается один главный поток
Ну, может быть, только если вот это вот заменить таким общим овалом, но смысл данного рисунка следующий: ядро знает об этих потоках, потоки создаются с помощью соответствующего системного вызова. Но владельцем ресурсов остается процесс, это очень важно, что владельцем ресурсов остается процесс, это, можно сказать, ключевой момент, влияющий даже на эффективность работы системы. По-другому не сделать потому, что начнется чехарда. Нельзя сделать потоки владельцами части ресурсов. Не пошли по этому пути. Владельцем ресурсов остался процесс. Но процессорное время получают потоки. Т.е. единицей диспетчеризации становится поток. В этом типе потоков так же единицей диспетчеризации является процесс. Процессу выделяется квант, а уже на уровне прикладных потоков происходит деление этого кванта между потоками, а здесь процессорное время, квант выделяется потоку. Как говорят, поток владеет счетчиком команд. Да, при этом, если в программа никакие потоки не создаются, то для общности управления в процессе создается один главный поток.
21:16 Две модели:
- однопоточная модель процесса
- многопоточная модель процесса
21:38
Где-то тут спряталась однопоточная модель
Ну, надо сказать что при советской власти больше издавалось книг и, в частности, по ос и переводных, вот, и, значит, если вам в руки попадут эти книги, вы можете увидеть там такое понятия как БУП. Ну и, кстати, наши соотечественники-авторы также использовали это понятие. Блок управления процессом. Если мы будем с вами разбирать линукс, то мы увидим понятие дескриптор. Ну, блок управления процессом по-другому - это дескриптор. Т.е. понятно, что система должна иметь полную информацию о том, чтобы управлять процессом. Ну давайте назовем дескриптор процесса, хотя у меня написано БУП.
Владельцем ресурсов остается процесс. Важный момент: поток своего адресного пространства не имеет и выполняется в адресном пространстве процесса. Поэтому потоки могут использовать глобальные переменные , называемые разделяемыми. Т.е. эти переменные объявляются в адресном пространстве процесса, а потоки могут с ними работать. В отличие от потоков, каждый процесс имеет собственное защищённое адресное пространство. Поток должен иметь как стек пользователя, так и стек ядра.
Убежала далеко вперед, такой курс. Значит, как я уже сказала, для общности, если в программе не запускаются потоки, то система создает один главный поток и вы видите, что поток должен иметь как стек пользователя, так и стек ядра. Чуть позже мы вернемся к этому.
26:40
Многопоточная модель.
Потоки должны иметь структуру, которая описывает поток в системе. Потоки имеют приоритеты относительно приоритета процесса. Каждый поток имеет стек пользователя и стек ядра.
Почему нужны отдельно стек ядра и стек пользователя? Потому что "невозможно писать все в один стек". Cтек пользователя: пользователь может создать в своей программе несколько стеков, пользователь может менять размер стеков в своей программе. В стек пользователя помещаются переменные, когда процесс (она говорила о потоках, а потом резко переключилась на процессы; скорее всего, с потоками аналогично) выполняется в режиме пользователя. Если процесс переключается в режим ядра, например при возникновении прерывания, необходимо сохранить аппаратный контекст. Если прерывание возникло, пока процесс выполнялся в режиме ядра, аппаратный контекст сохраняется в стек ядра. Поскольку размер пользовательских стеков может быть изменен пользователем, места в пользовательском стеке может не хватить (это еще одна из причин, зачем нужны два стека: пользовательский и ядра). Пользователь может объявить несколько стеков - в этом случае неоднозначно, в какой стек сохранять переменные (это еще одна причина).
Ну если бы у нас не было потоков, а были бы только процессы, мы могли бы сказать, что каждый процесс должен иметь в наших системах стек пользователя и стек ядра. Почему, почему нужны отдельно стек ядра и стек пользователя? Ну стек пользователя - это тот стек, который может располагать пользователь. Пользователь может создать в своей программе несколько стеков, пользователь может менять размер стеков в своей программе, но я так полагаю, что такой практики вы не имели, но, тем не менее это возможно, но, как мы сказали, процесс часть своего времени выполняет собстенный код и тогда он находится в режиме пользователя, а часть времени процесс выполняет реентерабельный код операционной системы и тогда он выполняется в режиме ядра, или мы такое определение не давали? Это чисто юниксовое определение. Я говорила, может быть, что разработчики юникс очень четко определили все понятия, в частности они таким образом определили процесс. Соответственно, переключившись в режим ядра, процесс так же может переключаться. Например, возникло какое-то прерывание и нужно сохранить аппаратный контекст, но это аппаратный контекст режима ядра. Прерывание аппаратные приходят вне зависимости о какой-либо работы, которую выполняет процесс, система не может контролировать в каком режиме она работает, когда возникает аппаратное прерывание. Конечно аппаратное прерывание возникает и система перейдет в режим ядра, но и в режиме ядра может возникнуть аппаратное прерывание. Например, идет какое-то обслуживание какого-то запроса, в это время пришло прерывание от системного таймера - наивысший приоритет. Сохранять все в одном стеке невозморжно, тем более пользователь мог объявить несколько стеков, тогда как определить системе в какой писать? Невозможно. Кроме того, как я уже сказала, вы могли изменить размер стека - стало недостаточно его памяти для того, чтобы сохранять все необходимые данные, ведь переключение возможно. Ну, в общем, действительно нельзя все писать в один стек. Поэтому в системе автоматически для ваших процессов создается стек ядра. Там, конечно, есть нюансы отдельные, но вот у каждого потока должен быть как стек ядра, так и стек пользователя. 35:50
Мотивы, которые привели к созданию потоков. Основным мотивом было стремление сократить накладные расходы на переключение контекста.
Хочу обратить ваше внимание на книгу Рихтера "Виндус для профессионалов", у него есть отдельная глава "Потоки" и у каждой главы у него есть введение, и в ведении он приводит несколько примеров удачного распараллеливания программ на потоки, я к тому, что кроме удачного может быть НЕ удачное. В частности, скажем, удачным распараллеливанием можно считать такое распараллеливание, когда, если процессу нужно что-то время от времени записывать в файл, то для этой работы подойдет выделение отдельного потока, это если сказать очень коротко.
Потоки полезные, когда процесс имеет несколько задач, которые могут выполняться независимо от других задач. Особенно это выигрышно, когда одна задача может блокироваться, а другие задачи в это время могут продолжать свое выполнение без блокировок. Например, текстовый редактор: Некоторый, скажем, низкоприоритетный поток может проверять грамматику, в то время как другой поток загружает изображение с диска в память. Третий поток выполняет периодически автоматическое резервное копирование в файл.
Я напоминаю вам, что обычные файлы предназначены для долговременного хранения информации, находятся на долговременной памяти (на внешней памяти, например, на дисковой памяти - это устройство).
Обращение к диску выполняется через прерывание - это внешнее устройство, значит процесс блокируется. Другой пример - веб-сервер: многопоточность позволяет такому веб-северу одновременное выполнение нескольких запросов, без необходимости их обслуживания последовательно и без необходимости создания отдельного процесса для каждого пришедшего запроса.
До появления концепции потоков было сделано так: Демон слушал код и создавал процессы - потоки для каждого пришедшего запроса. После этого выполнялось возвращение исходного процесса к прослушиванию порта (в следующем семестре мы будем выполнять лабораторную работу по сокетам с мультиплексированием, это как раз замена многопоточности).
00:10(вторая часть лекции)
Достоинства многопоточности
- Отзывчивость. Имеется в виду, что один поток может обеспечивать быструю реакцию, когда другие потоки, например, выполняют большой объем вычислений, т.е. поток может быть интерактивным, в то время как другие потоки выполняют большой объем вычислений.
- Разделение ресурсов. Поскольку потоки выполняются в адресном пространстве процесса, они могут обращаться к общим данным.
- Экономичность. Здесь имеется в виду переключение контекста т.е. когда мы переключаем потоки, то переключается только аппаратный контекст, потому что владельцем ресурсов является процесс. Но тут имеется оборотная сторона: если подряд выполняется несколько потоков одного процесса, то переключение полного контекста не выполняется (только аппаратного), но если происходит переключение на поток другого процесса, то происходит переключение полного контекста. Но среднестатистически существует выигрыш, особенно учитывая размеры современного ПО, которые приводят к другим проблемам. Современные огромные программы делятся на очень огромное количество потоков. Потоки выполняясь запрашивают память. Например, потоку понадобилось 1000 байт, значит, в принципе, будет выделена страница (страница 4000, а нужно только 1000). Страница будет помечена как занятая. Дело в том, что владельцем ресурсов является процесс, потоки запрашивают и освобождают память. Вот он эту 1000 использовал, а потом завершился. Страница осталось помечена как занятая, значит экономичность подчеркивает, что переключение аппаратного контекст менее затратно.
- Масштабируемость. Однопоточные процессы могут выполняться только на одном процессоре, а многопоточные могут использовать многопроцессорность. Например, в наших компах 4 ядра - 4 процессора. (И возникает опять проблема, если у вас значительно большее количество потоков.)
08:33
Проблемы(трудности) многопоточности
- Определение задач путем анализа приложений для определения действия, которые могли бы выполняться параллельно. Это надо выполнять осмысленно, обоснованно.
- Сбалансированность. Поиск задач для параллельного выполнения, которые имеют одинокую по масштабу функциональность. Другими словами, не стоит тратить потоки на тривиальные задачи.
- Общее адресное пространство и возможность разделения переменных. (это и достоинство, и источник проблем) Следует предотвращать взаимодействие потоков друг с другом.
- Зависимость данных. Если одна задача зависит от результата выполнения другой задачи, то эти 0задачи должны быть синхронизированы, для того чтобы обеспечить доступ к данным в правильном порядке.
- Тестирование и отладка. Для многопоточных приложений тестирование и отладка являются более сложными, поскольку необходимо определить условия приводящие к так называемым гонкам. В результате такого тестирования необходимо выявить участки кода, в которых возможны эти гонки. Это опять же связано с разделение одних и тех же данных
13:50
Теоретически имеется два типа параллелизма (два пути для распределения нагрузки):
- параллелизм данных (data parallelism)
- параллелизм задач (task parallelism)
15:06
Распараллеливание данных
Разделение данных между несколькими ядрами и выполнение одних и тех же задач для каждого подмножества данных. Например, разделение большого изображение на фрагменты или участки и выполнение обработки каждого фрагмента на отдельном ядре, т. е. в отдельном потоке.
16:50
Распараллеливание задач
Разделение разных задач для выполнения на разных процессорах одновременно. Например, текстовый редактор: там разные задачи могут выполняться параллельно.
18:13
Multi threading models. Многопоточные модели.
Мы имеем два типа потоков: User thread и kernel thread.
Many to one. Многие к одному.
Буква К - Kernel thread. Для системы базовой абстракцией остается процесс. Хочу обратить на это внимания. Т.е. это как бы произвол автора. На самом деле, на мой взгляд, это не совсем корректно. С другой стороны процесс, я не знаю как его можно назвать Kernel thread'ом.
Я уже объясняла и скажу коротко. Естественно, например, это могут быть так называемые зеленые потоки саларес (Green threads solaris). Это могут gnu portable threads. Значит это posix ansi, т.е. эти gnu portable(не уверен) threads основаны на posix ansi.
One to one. Один к одному.
Не могу не отметить некорректность данной модели. Получается, что это какие-то свободные потоки, которые блуждают свободно. Но, как я сказала, базовой единицей декомпозиции системы есть и остается процесс. Потоки запускаются в коде определенных программ. Вы создаете эти потоки, используя специальные системные вызовы или они могут создаваться MSI автоматически, но, как говорят, не всегда такое автоматическое распараллеливание на потоки дает эффективное выполнение приложения. Очень часто получается, что приходится переписывать потоки вручную. Т.е. вот так рисовать все таки нельзя. Эти потоки принадлежат определённому процессу.
Относительно этой модели необходимо сказать, что не может быть в системе запущено произвольное количество потоков. В системе имеются ограничения по количеству потоков, которые могут быть созданы. Здесь отмечается, что эта модель характерна для таких ос как Linux, Windows 95, XP. То, что они рисуют, некорректно. Эти потоки все равно создаются в рамках определенных программ. Планирование выполняется с учетом приоритета потока, но базовым приоритетом является приоритет процесса.
Many to many. Многие ко многим.
Мультиплексирует любое количество потоков уровня пользователя на равное или меньшее количество потоков ядра. При этом пользователь не имеет никаких ограничений на количество создаваемых потоков.
Планирующие(?) системные вызовы не блокируют процесс полностью, процесс может быть разделен между многими процессорами. Эта модель описывает так называемые легковесные процессы (light weight). Это известное название в мире Unix/Linux. Дело в том, что создание потока требует определенных накладных расходов. В системе могут существовать такие (light weight) потоки и когда поток уровня пользователя создается, ему может быть назначен такой (light weight) поток. Как красиво говорят, точка входа в поток вбрасывается в этот облегченный поток. Вот эта модель коррелирует с двухуровневой моделью. Здесь поддерживается как многие ко многим, так и один к одному. Эта модель применяется в Solaris.
36:49
Стандарт Posix
Posix потоки: их принято называть pthreads. Могут создаваться и как потоки режима пользователя, и как потоки режима ядра. Существует стандарт Posix (IEEE 1003.1) Вот эти потоки Posix доступны (available) в Solaris, Linux, MAC OS X.
OpenMP - это автоматические генерирование параллельных потоков, автоматическое распараллеливание кода. Но часто не позволяет получить оптимального решения, т. е. анализ выполнения таких приложений приводит к необходимости распараллеливать приложения в ручную.
Вот видите, потоки являются актуальной интересной темой с ростом объемов ПО. Современные все приложения являются многопоточными. Для персональных компьютеров это связано с ограничениями - у нас с вами не супер компьютер с огромным количеством процессоров - но та же проблема может возникнуть и в сетях. В сетях эти проблемы усугубляются удаленным взаимодействием, когда вообще можно даже не понять вовремя проблемы, которые возникли, поскольку все это связано со временем доставки сообщений, а значит со временем взаимодействия процессов между собой. Естественное, если речь идет о распределенной системе, там не потоки а процессы. Но проблемы эти очень важны.
конец