null

Многопоточность для самых маленьких

Краткий экскурс в то, что из себя представляет многопоточность.

Обычно под многопоточностью подразумевают способность выполнять какой-то набор действий параллельно. По-хорошему, я бы использовал слово не многопоточность, а многозадачность и привёл бы аналогию с тем. как человек может одновременно есть и залипать в телефон. Он выполняет одновременно две задачи. В компьютере примерно также. Можно воспроизводить музыку и играть в Starcraft. Но, если у человека две руки, то вопрос в том, что там внутри компьютера такого происходит? У него тоже есть нечто в двух экземплярах?

Для того, чтобы понять, что же это за нечто, которое есть в комплекте двух или более штук, стоит вспомнить что такое задача с точки зрения компьютера. Задача - какая-то программа. А программа - это просто код, который берёт какие-то данные и что-то считает. 

Нужно понимать, что код - это  просто лежащие в памяти значения (0 и 1 если вам станет проще). Есть процессор и он умеет испольнять какие-то команды. Процессору можно сказать следующее: "Смотри, вот тебе адрес. Это адрес, где лежит программа в памяти. Давай ты будешь по этому адресу брать из памяти данные и их пытаться распознать как команду и исполнить. А потом брать следующую.

Отлично. Исходя из этого, получается, что если программа выполняется, значит внутри процессора лежит адрес той строки кода (инструкции) на которой он сейчас остановился. Аналогию можно произвести с готовкой какого-нибудь блюда на кухне. У вас есть рецепт. Вы сейчас на шаге номер 6. Варите рис. На кухне вы используете какую-то посуду, пользуетесь плитой, ножом, разделочной доской и т.п.  Вы пришли туда со своими продуктами, но пользуйтесь уже имеющимся оборудованием. С программой также. Вы из памяти принесли какие-то данные и в какой-то момент времени вы используете определённые емкости процессора, чтобы держать в них промежуточный результат вашей готовки (в процессоре такие места для хранения промежуточного результата зовут регистрами). Помимо емкостьей (регистров), вы ведь пользовались плитой. Она что-то делает с вашими продуктами (данными). Вы в неё положили рис и воду, а она вам через какое-то время вернула сваренный рис. Это по сути блок, который выполняет некое действие, в процессоре тоже есть такие блоки. Они занимаются тем, что применяют некие операции над данными. Например они могут сложить два числа, могут сделать операцию исключащего ИЛИ и многое другое. Один из таких блоков в процессоре называется АЛУ. Он как следует из расшифроки выполняет арифметические и логические операции. 

А теперь представим, что нам нужно, чтобы на кухне одновременно можно было готовить нескольким людям (допустим, что это общага). Для этого, вам нужно две вещи. Во-первых, вам нужно иметь две кастрюли (второй набор регистров), иначе вы просто не положите никуда ваши продукты(данные). А ещё вам нужно иметь две комфорки (два блока для произведения вычислений). 

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

Вообще, в современных процессорах, такой вот набор называется ядром. У вас на одном процессоре, есть два комплекта регистров и блоков для произведения вычислений. Это и есть многоядерность.

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

Всё очень просто. Приведу другую аналогию, представим, что у нас всё ещё есть общежитие. Есть комната, в которой живёт 3 человека. Иногда каждый из них хочет привести свою подругу и вместе с ней посмотреть фильм. Но, они не могут делать это одновременно. Поэтому товарищи договариваются, что сегодня вечером, двоё из них уйдут поиграть в бильярд, а третий займёт ценный для всех ресурс (комнату). В другой вечер они поменяются. С людьми всё хорошо и просто. Они договорились, что будут разделять ресурс по времени. Распределят его и выделят каждому какое-то время на пользование им.

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

А как тогда это происходит? А происходит это просто. Существует некий арбитр, который знает о существовании всех тех, программах, которые хотят сейчас работать. И он управляет доступом к этому ценному ресурсу. Этот арбитр -  операционная система (ОС). Она может контролировать ресурсы и предоставлять их во владением разным программам. В нашем случае, она даёт им возможность испольняться на процессоре. Распределяет ресурс она очевидным образом. Она выделяет каждой программе какое-то кол-во времени (квант) и загружает эту программу (просто перепрыгивает на это место и даёт процессору испольнять команды с этого места). По прошествии этого времени, она снимает его оттуда и даёт возможность пользоваться процессором другим программам.

Всё выглядит очень логично. Но я уточню. Вы ведь знаете, что ОС - это тоже программа? А как тогда она может прервать исполняющийся на процессоре код, если у нас всего одно ядро? Для этого ей нужно выполнять инструкции, и получается, что если программа не вернёт сама управление ОС, то она будет исполняться столько, сколько хочет. 

Конечно же есть решение этой проблемы. И нет, нет такого, что процессоры на самом деле двухядерные, просто одно всегда используется ОСью. Объясняю как это происходит. Внутри процессора есть специальный механизм прерываний. Он позволяет при наступлении какого-то события остановить текущий выполняющийся код и передать управление в заранее определённое место. Этот механизм очень много где используется. Например, с помощью него обрабатываются сигналы от многие внешних устройств (клавиатуры, сетевой карты и т.п.).  Также можно сделать так, чтобы прерывание возникло в результате какой-то команды. Т.е. можно сгенерировать его программно. Зачем? Это вне контекста нашего диалога, поэтому о прерываниях от внешних устройств. Есть в процессоре такая специальная, под названием таймер. Она выполнена на уровне железа и вы как программист, можете настроить его на какое-то время. Когда это время пройдёт, то таймер сгенерирует сигнал (прерывание) и процессор автоматически переключится на тот код, который должен его обрабатывать.    

Механизм прерываний и таймер позволяют нам сделать следующее. Наша ОС работает на процессоре. Она настраивает таймер и передаёт выполнение другой программе (изменяет адрес следующей инструкции, которую нужно исполнить). Какое-то время наша программа работает, но когда таймер доходит до 0, происходит прерывание. Наша программа останавливается и автоматически переходит в обработчик этого прерывания. Конечно же, автоматически сохраняется то место на котором мы остановились и те значения регистров, которые были в момент остановки программы. Далее обработчик передаёт управление ОС. ОС в памяти держит определённые структуры, в которых она хранит информацию о исполняющихся программах. Хранит также когда они последний раз исполнялись на процессоре, а также контекст программы (регистры, адрес последней выполненной инструкции) который был у этой программы в момент её остановки. Исходя из имеющихся у ОС данных, она может выбрать какой программе нужно следующей дать возможность поработать на процессоре. Она выбирает эту программу. Снова настраивает таймер, загружает контекст этой программы и передает ей исполнение. И так будет происходить всё время.  Вопрос: а откуда вообще ОС узнает о существовании других программ? Ответ простой, когда вы запускаете программу, то вы это делаете через механизмы ОСь и в момент её запуска вы даёте информацию ОС о том, что есть такая программа, она лежит здесь в памяти и её надо исполнять. После этого весь контроль над вашей программой под властью ОС.

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

Надеюсь после прочтения этой статьи у вас что-нибудь прояснится. На этом, пожалуй, откланяюсь!