Как стать автором
Обновить

Комментарии 45

> Если Вы посмотрите исходники, например, ядра Windows
Где можно посмотреть?
Как-то раз исходники Вин2К ушли в Интернет. Там и можно, наверное…
Пошарьтесь по интернету. Не все университеты закрывают доступ к выданному им по академическим программам. Ну и в eDonkey это всё есть.
Можно получить в MS по академической программе.
Гуглить windows research kernel download.
Например: здесь.
Да ну. Нить — это прежде всего способ исполнения программы (задача), а никакой не виртуальный процессор. И это крайне важно понимать. Потому что ядро операционных систем в рамках своей модели задачи с нитями делает очень много всяких штук, кроме сохранения и восстановления их контекста.

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

Да даже в том же Windows. Ресурсы процессора включают в себя не только регистры общего состояния, указатель стэка и указатель инструкции, но и кучу всяких других регистров, которые никак не 'виртуализируются'. Вот… Поэтому, уважаемый <hh user = «sysprg» \>, вы всё же предлагаете слишком простую модель нити, которая от сложной действительности достаточно далека.
Нет, не согласен. И легковесные, и настоящие threads подразумевают виртуализацию регистров процессора, их сохранение при диспетчеризации – они всегда эмулируют множество процессоров с индивидуальными значениями в их регистрах. В регистрах живут локальные переменные процедур, и даже если нет преемптивной многозадачности, то все равно нужно обеспечивать каждому thread свои индивидуальные регистры, как будто бы он исполняется на настоящем отдельном процессоре. Даже setjmp в C сохраняет значения регистров, что уж говорить о любой реализации многозадачности, даже если она не преемптивная. Даже у самого тривиального (быть может, в свою очередь тоже эмулируемого) стекового процессора, у него все равно есть указатель команд и указатель стека. Их и придется виртуализировать, чтобы создать абстракцию threads. Давайте не будем вносить путаницу и называть тредами гипотетические средства параллельного исполнения в скриптовом языке, не транслируемом никогда в машинный код – так можно далекой зайти. Если же мы говорим об универсальном решении, то неизбежно у нас возникает задача виртуализации процессора с его регистрами.
А существует необходимость ассоциировать нить именно с процессором? Например, в какой-нибудь системе вроде Erlang или Oz нити могут исполнятся на процессорах разной архитектуры и периодических прыгать по этим самым процессорам без ущерба для функциональности.

И тогда вся теория про то, что нить — это способ виртуализовать процессор ломается. Ведь, возникает вопрос: а какой именно процессор?

Вот. И не понятно, почему вам не нравится определение 'нить — это контекст для исполнения программы'. Контексты и программы бывают разные, со всеми вытекающими последствиями в виде разнообразного мира.
Да, не спорю — можно и так, можно сказать, что это контекст исполнения программы. Но хотя с точки зрения абстрактной такое определение будет верным и по-своему красивым, начинающему программисту оно не дает понимания того, что такое threads в обыкновенных современных ОС, как они там работают. У определения через виртуальные процессоры есть все-таки связь с суровой реальностью, данной нам в ощущениях (с устройством ОС) и оно что-то объясняет программисту, который хочет разобраться в реализации threads. Можно понять, что же примерно делается в обыкновенных ОС — что они сохраняют состояние процессора и переключаются между сохраненными состояниями и этим достигают видимости одновременной работы многих процессоров. Ваше определение показывает, как это выглядит в языках полностью абстрагирующихся от железа и вообще от деталей устройства компьютера, но не дает понимания откуда взялись threads на уровне ОС и что же они там-то представляют из себя.
Что же касается того, что не весь регистровый файл виртуализируется (не создается полной копии процессора), то это обусловлено как техническими сложностями, так и логической ненужностью виртуализации, например, регистров, отвечающих за страничную память. Если все threads приписанные к одному процессу по определению разделяют между собой одно адресное пространство (так как адресное пространство это и есть сам процесс), то нет нужды сохранять регистры, отвечающие за страничную память. Другие регистры не сохраняются потому, что они предназначены для разработчиков самой ОС и не используются в прикладных программах. Часто их виртуализация вообще невозможна ввиду архитектуры CPU. Но всегда виртуализируется весь регистровый файл application уровня — GPR, floating-point регистры, флаги прикладного уровня, всякие SIMD-регистры.
Ну и вот. Если половина процессора предназначена для самой ОС, то о какой 'виртуализации' его при помощи абстракции нити можно говорить? Поэтому я и настаиваю на том, что концепция нити — это способ исполнения программы, а никакая не виртуализация. Процессор самый настоящий в этом деле используется, а нити — это способ управлять этим процессором.
На процессор (как и на любое технически сложное устройство) можно смотреть по-разному. Можно с точки зрения пользовательской и тогда threads его прекрасно виртуализируют. Можно с точки зрения специалиста по системному программированию, читающего книги типа «кое-что об устройстве современных процессоров» в трех томах, и тогда не приходится говорить ни о какой виртуализации. :)
Кстати, можно добавить, что и нить сама по себе не является чем-то исполнимым в Windows. Одних регистров мало — нужно ещё где-то размещать хотя бы код. А это уже память. Так что тут такой вот симбиоз.
Да, с этим согласен.
1. Во всех «сурьёзных» книгах по IT русских книгах используется термин «поток» — он явно более адекватно описывает происходящее. «Нить» — это буквальный перевод с английского, и использовать этот вариант не нужно. Сравните:
«поток» часто используют в контексте потоков ввода-вывода. потому лучше использовать слово «нить».
Там используется слово stream. Это недостаток русской терминологии. Обычно употребляют слово поток (thread) и в тоже время поток (stream), просто конкретизируют в зависимости от контекста.
в общем, не вижу ничего плохого в слове «нить». есть уже предостаточно переводной литературы, где оно используется и вполне обоснованно, как я указал выше.
«правил» в таких вещах вообще не бывает, весь вопрос в частоте употребление и в едином смысловом поле.
вне контекста, слово поток слишком многозначно.
Лучше говорить thread и stream. Путаницы не будет точно
бездуховно :) но приходится
IMHO, но по ощущениям thread всё же ближе по смыслу к слову нить, как нить рассуждений, путеводная нить. То есть, некий линейный и последовательный объект, как последовательность инструкций. Даже в английском thread — это если и поток, то — очень тонкая струйка. Stream — это же нечто похожее на течение реки. Так что нить — вполне себе оправданный термин.
2. Вообще вся дискуссия кажется высосаной из пальца. Между терминами «процесс» и «поток» нет никакой фундаментальной разницы. Лучше всего это видно на примере Linux'а: там между этими сущностями нет разделения вообще (оба порождаются одним и тем же системым вызовом, могут разделять и не разделять ресурсы, etc). Единственная разница между процессами и потоками — то, что процессы идентифицируются PID'ами самого потока, а нити создаются с флагом CLONE_THREAD и потому getpid(2) в них возвращает PID предка… Нужно это как раз для поддержания иллюзии того, что это разные сущности (многие библиотеки на иное обижаются).
Я раза три-четыре читал статью, и никак не понять, о чем речь, о какой виртуализации автор пишет, ничего не понимаю.
Во-первых, Поток это Stream,я всем настоятельно рекомендую использовать западную терминологию и не пытаться найти ей русскоязычный аналог. Stream = стрим, скорее всего речь идет о потока ввода/вывода (чтение байтов из файла, чтение массива байтов на сетевом соединении и т.д.).

Thread = тред. Речь идет о… (как-бы это сказать своими словами, чтоб было просто и понятно, и при этом не исказить смысл...). Тред, это подпрограмма, порожденная основной программой, тред живет, пока живет программа, породившая его. Программа может прекращать работу тредов. Так же тред может работать даже после завершения программы — это тред-демон (daemon-thread), особый тип тредов.
Сама программа, тоже работает в своем, главном треде. Если программа во время своей работы создает треды, они становятся наследниками основного треда.
Соответсвенно, операционная система отвечает за создание тредов, в которых исполняются программы: браузер Хром, MS Word. Эти треды работают параллельно. Как это получается — уже второй вопрос, касающийся архитектуры операционной системы.

Допустим, у нас есть 10 бегунов, каждому из которых нужна дорожка. Есть тренер, расставляющий бегунов по беговым дорожкам. Так вот, тренер — это операционная система, или другая программа, внутри которой могут порождаться треды. Тренер хватает бегуна за шкирку (программу, запущенную пользователем) и ставит его на свою дорожку (тред). Пока бегун несется к финишу, ему может стать скучно, тогда он зовет своих друзей (треды, порождаемые программой), раскладывает рядом дорожки для них, позволяя бежать рядом с собой. Раскладыает, естественно, с разрешения тренера.

Практический пример: есть программа, читающая файлы с двух физически разных дисков. Данные должны быть считаны, загружены в память, а затем сохранены.
Если это приложение будет работать только одим тредом, то файлы будут открываться попеременно, то с первого, то со второго диска модулем программы, отвечающим за чтение файлов и выгрузку данных в память.
Есть возможность оптимизировать этот процесс.
Написать модуль чтения данных и загрузки в память таким образом, чтобы одновременно могло быть два таких модуля.
Программа загружает два одинаковых модуля, но каждый из них работает со своим физическим диском.
Теоретически, скорость загрузки данных возрастет в два раза.
Далее, данные нужно обработать и сохранить на диск.
Зачем для этого держать всю программу в памяти? Оставляем один тред-демон, который занимается обсчетом данных и записью их на диск, а саму программу закрываем.

Как-то так…

я всем настоятельно рекомендую использовать западную терминологию и не пытаться найти ей русскоязычный аналог
Either we are using English and then English terminology is obviously fine — or we are using Russian и тогда у нас есть потоки и процессы. Не надо умножать сущности без необходимости.

Так же тред может работать даже после завершения программы — это тред-демон (daemon-thread), особый тип тредов.
Где вы такое вычитали? В системах где потоки и процессы являются разными сущностями процесс — это контейнер для потоков. Процесс может завершить работу только с последним потоком. Иногда выход из главного потока убивает все остальные, иногда — процесс существует пока есть хотя бы один поток, но если поток жив, то жив и «охватывающий» его процесс.

1. Хорошо, если у нас есть потоки и процессы, тогда как быть со stream=потоками?
2. Я глупость написал. Демон, это фоновый, обслуживающий тред, Это помощник, который едет на машине рядом с бегуном и время от времени дает ему воду или полотенце. Как только бегун (тред) добежал до финиша (работа треда, или всей программы окончена), помощник останавливается (демон-тред завершает работу).
Большое спасибо, что поправили меня!
Так это как раз следствие того, что с самого начала в Linux не предусмотрели threads и затем их пришлось делать через процессы, разделяющие одно адресное пространство. Однако если операционная система с самого начала проектируется с threads, то там четко есть два разных объекта. Для высокой производительности как раз важно отличать их друг от друга. Даже Linux на самом деле вынужден учитывать особенности threads в планировщике, особенно для SMP, SMT и мультиядерной архитектуры. Формально оформляя threads как процессы, в планировщике они вынуждены признать, что эти процессы особенные, например, у них у всех одно адресное пространство и нужно оптимизировать переключения задач так, чтобы поменьше переключаться между пространствами и не трешить TLB и прочие внутренние буферы процессора.
Для высокой производительности как раз важно отличать их друг от друга. Даже Linux на самом деле вынужден учитывать особенности threads в планировщике, особенно для SMP, SMT и мультиядерной архитектуры.
Он там учитывает много чего: квоты на группы, доступ к памяти и т.д. и т.п.

Формально оформляя threads как процессы, в планировщике они вынуждены признать, что эти процессы особенные, например, у них у всех одно адресное пространство и нужно оптимизировать переключения задач так, чтобы поменьше переключаться между пространствами и не трешить TLB и прочие внутренние буферы процессора.
И что? Они, например, вынуждены учитывать тот факт, что на NUMA-системах мигрировать процесс с ноды, в которой живёт большая часть этого процесса нежелательно — по этому поводу ещё какую-нибудь сущность введём? И тоже будем утверждать что она суперфундаментальна?
Что Вы хотите мне доказать? Что в ядре Linux нет threads в общепринятом смысле слова? Я не собираюсь с этим спорить. Однако threads как способ организации вычислений в программах уже стали классикой абсолютно во всех универсальных ОС, включая Linux. Они есть в Linux в виде POSIX threads и почти все мало-мальски сложные программы их используют. И для прикладного программиста они отличаются от процессов именно тем, что разделяют общее адресное пространство. Именно с учетом этого и таким образом их используют при написании кода. В детали же реализации ядра Linux заглядывают только специалисты по этому самому ядру (и может быть какие-то единичные самые хитро-системные программисты, которым недостаточно POSIX API для работы с threads). Поэтому не вижу никакого смысла бежать от понятия «thread» и бороться со всяким упоминанием этого «крамольного понятия» только на том основании, что в ядре Linux они реализованы как частный случай процессов, разделяющих одно адресное пространство.
Я слышал, что в ОС Windows есть понятие fiber… Что они из себя представляют и как вы предлагаете переводить это понятие?
Представляют они из себя попытку компенсировать кривые руки. Как переводить — неизвестно. Нити — было бы самым разумным переводом если бы это название не использовали для потоков.

Грубо говоря процесс развивался так: вначале были только процессы, но их создание было безумно дорого. И работа с разделяемой памятью была затруднена. А fork вообще отсуствовал. В результате появилась заплатка: потоки. Грубо говоря процессы, которые бы разделяли все ресурсы (кроме процессора на SMP-системах) между собой. Но они всё ещё были слишком дороги (Linux создаёт новые процессы быстрее, чем Windows — новые потоки) и потому кто-то придумал заплатку к заплатке: вот эти самые fibers. Это новомодное изобретение известно уже много лет, но, как обычно, Microsoft решил всё «улучшить» и потому fibers в Windows по интерфейсу больше похожи на потоки, чем на классические сопрограммы.
Нет, это совсем не так, треды в Windows никогда не были «подпоркой».
Они унаследованы ей из OS/2 API, а там они появились как концептуальное решение безусловно гениальных дизайнеров данной ОС, определившей по сути весь дизайн Windows 32 API и повлиявшей на множество других современных систем. Еще раньше в широко распространенных системах с идеей тасков, исполняемых параллельно в одном адресном пространстве, работали на майнфреймах IBM. Возможно, что оттуда и пришла идея threads в OS/2, а затем и в Win32 API и в POSIX threads (интересно, что название внутренней структуры для thread в OS/2 (и Windows) и OS/370+ одинаковое – TCB, хотя бука «T» и имеет разное значение, thread vs task).
Переключение полноценных процессов всегда было очень дорогой операцией и на большинстве процессоров таковой и по сей день и является. Полноценные процессы имеют индивидуальные адресные пространства со своими страничными таблицами и их переключение чистит TLB и вносит кучу прочей сумятицы в деятельность процессора. Это почти полный останов процессора, фактически, ну разве что кэш не чистится (слава Богу). Поэтому и на x86, и на прочих процессорах в архитектуру вносят всякие подпорки, призванные снизить impact от переключения полноценных процессов (адресных пространств). Например, системные станицы на x86 могут быть помечены как глобальные и их дескрипторы не будут вынесены из TLB при смене адресного пространства. Но для юзерских станиц такого решения нет.
Решить проблему может только сильный редизайн железа, который возможен и опробован на тех же мэйнфреймах IBM, у которых процессор может регулярными машинными командами работать сразу со многими (!) адресными пространствами, для чего в ядре процессора аппаратно поддержана соответствующая организация всех системных таблиц управления памятью.
Спасибо. Ох уж эти трудности перевода.
Почитайте вот эту статью habrahabr.ru/blogs/system_programming/40227/

Fiber — волокно, эдакий легковесный трид уровня процесса, переключать и диспатчить который нужно программисту.
Спасибо, буду изучать :)
Это по сути такие же threads, только переключаемые исключительно через обращение к системному вызову, без преемптивной многозадачности, при которой операционная система может прервать выполнение thread по таймеру в любой момент времени (даже вне обращения к syscall).
У fibers есть свои сохраненные значения регистров, они очень похожи по своему поведению на «настоящие» threads. Настолько похожи, что их можно конвертировать в thread вызовом API ConvertFiberToThread. В поздних версиях Windows есть так же вызов, с помощью которого программа может проверить исполняется ли она в контексте «настоящего» thread или в контексте fiber.
P.S. Нафига они нужны нормальной программе — понятия не имею, наверное только для портации унаследованного кода, как и написано по ссылке которую привел khim.
Спасибо, для себя запомнил, что они не стоят отдельного внимания.
sysprg, с моей точки зрения, вы поторопились, смешав треды (в об0ем-то абстракции, которыми пользуются программисты), с процессами, протекающими в системе, вызванными порождением тредов. Из-за этого статья производит впечталение каши: вы вроде много знаете, хотите много интересного рассказать, уложившись в три с половиной строчки (я утрирую). Может, имеет смысл рассказать, что такое тред, затем рассказать, то, как реализована многозадачнгость в разных операционных системах, а после — как треды, абстрактные сущности, реализуются на уровне системы.
может, стоит просто скопипастить все объяснения у Таненбаума? ^_^
НЛО прилетело и опубликовало эту надпись здесь
Главная идея thread – это виртуализация регистров центрального процессора – эмуляция на одном физическом процессоре нескольких логических процессоров, каждый из которых имеет свое собственное состояние регистров (включая указатель команд) и работает параллельно с остальными.

А разве планировщик ОС не делает тоже самое для отдельных процессов, многозадачность ~ «эмуляция на одном физическом процессоре нескольких логических процессоров». А идея thread в распараллеливании работы отдельного процесса, более дешевом, чем распараллеливание с помощью отдельных процессов.
Предлагаю выбрать что-то одно в качестве единицы исполнения (пусть это будет thread), а что-то другое – как контейнер. Пусть это будет процесс. Пусть он имеет свое адресное пространство и объединяет те threads, которые работают в этом адресном пространстве.
Если у нас будет сразу две базисных единицы исполнения (и процесс, и thread), то мы сами себя запутаем.
Можно пойти по другому пути, как в раннем Linux. И thread вообще не вводить как понятие на базисном уровне. Сказать, что это «просто такие особые легковесные процессы». Но threads сейчас используются миллионами программистов по всему миру, в том числе и в Unix (pthreads).
И поэтому все равно придется в современном мире их вводить как понятие на пользовательском уровне. И в ядре тоже есть куча всего, связанного с поддержкой threads – опять-таки, придется их упоминать в коде и в документах для описания соответствующих частей ядра. Смотрим, например, современные исходники ядра Linux. Слов thread встречается более, чем в 2000 файлов!
И чтобы внести ясность логично только thread и оставить в качестве элементарной (а не производной) единицы исполнения. Зачем две разных исполнимых сущности на одном уровне абстракции?
Если мы хотим для себя построить ясную систему понятий, а не просто описать одну любимую исторически сложившуюся ОС, то логично разделить понятия thread и процесса (контейнера, содержащего (в том числе) и threads работающие в одном адресном пространстве).
Предлагаю выбрать что-то одно в качестве единицы исполнения (пусть это будет thread), а что-то другое – как контейнер.
А кому и зачем он нужен — этот контейнер? Почему связанные между собой процессы (неважно — общее у них адресное простанство или нет) просто не поместить в группу процессов? Google Chrome — это одна программа или 100?

И в ядре тоже есть куча всего, связанного с поддержкой threads
Где, я извиняюсь? Есть несколько флагов в одной функции и несколько хинтов для планировщика (притом что он учитывает вагон других вещей в своей работе).

Смотрим, например, современные исходники ядра Linux. Слов thread встречается более, чем в 2000 файлов!
А вы эти исходники смотрели или только grep напустили? Слово thread используется в ядре совсем не в том контексте, в каком вы бы этого хотели: threads в ядре вовсе не обязаны разделять адресное пространство! Более того — адресного пространства там может просто не быть (так называемые kernel threads). Никакого отношения к Posix Threads эти структуры не имеют и иметь не могут: на уровне ядра этих вещей просто нет.

Если мы хотим для себя построить ясную систему понятий, а не просто описать одну любимую исторически сложившуюся ОС, то логично разделить понятия thread и процесса (контейнера, содержащего (в том числе) и threads работающие в одном адресном пространстве).
Зачем всё это нужно? Да, в ядро Linux'а было добавлено несколько трюков, которые позволяют делать NPTL вид что потоки и процессы — разные сущности (скажем getpid в NPTL-потоке возвращает PID не самого потока, а его предка, что позволяет считать PID самого потока TID'ом). Но затронуто этим процессом было с десяток функций — никакой новой сущности при этом никто не вводил…
Надеюсь, Вы не будете спорить с тем, что threads стали неотъемлемым атрибутом современного программирования? Хотя бы в виде POSIX threads в unix-подобных системах.
В чем цель моей маленькой статьи? Как можно проще объяснить человеку изучающему системное программирование, но еще не знающему, что такое threads их суть. Не рассказать КАК они реализованы в деталях В ОДНОЙ ЕДИНСТВЕННОЙ системе (ядро которой рассматривает threads как процессы), а объяснить САМО ПОНЯТИЕ.
Что за бессмысленная и беспощадная мода российских форумов — все деструктировать постоянными отрицаниями всего сущего, бесконечно глубокими въедливыми копаниями где чего нет… Не нравится Вам мое определение theads — дайте свое, только универсальное для разных ОС, которое можно было бы применить и к Windows, хотя бы. Нравится она Вам (или мне) или нет — но это самая распространенная система в мире.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории