#новичкам
Чтобы ввести термин "поток" надо очень верхнеуровнего понять, как вообще код исполняется на ядре.
У нас есть 2 важных компоненты, без которых ничего не возможно. Это память и CPU. CPU скармливаются данные из памяти, он их пережевывает и выплевывает другие данные, которые также записываются в память.
CPU нужны операнды и инструкция, что с ними делать.
Начнем с инструкций
У нас есть скомпилированная программа и она в том числе содержит непосредственно код программы. Грубо говоря, это полотно инструкций, которые одну за одной надо исполнить на CPU.
Машинный код программы хранится в памяти процесса и для него нужен какой-то механизм исполнения.
Чтобы отслеживать, какая инструкция будет выполняться следующей, нужен специальный Program Counter(PC) или счетчик инструкций. Процесс такой: CPU загружает инструкцию, на которую указывает PC, увеличивает счетчик, декодирует инструкцию, делает нужные преобразования над операндами, сохраняет результат и цикл повторяется снова.
Теперь операнды
Для операций процессор чаще всего берёт операнды из регистров общего назначения, куда они предварительно загружаются из памяти.
Из какой?
Из 3-х областей памяти: стека, кучи и сегмента глобальных данных. Куча и глобальные данные шарятся между потоками исполнения и принадлежат процессу.
А вот как раз стек и связан с непосредственным обеспечением исполнения кода в моменте. Он нужен для работы функций. Каждый фрейм стека соответствует одной вызванной, но ещё не завершённой функции. Как только в коде вызывается функция, на стек кладется информация о ней. В основном это локальные переменные и адрес возврата. Так информация об исполнении всех вызванных в данный момент функциях остается в памяти.
Есть 2 важных регистра, которые связаны со стеком. Это base pointer(указатель на начало блока самой последней вызванной функции) и stack pointer - указатель на вершину стека.
По итогу информация из памяти(стека, кучи и тд) загружается в регистры общего назначения, откуда ею может пользоваться сам процессор.
Вот это очень грубо то, как исполняется код на процессорах.
Но этот процесс описывает исполнение одной программы. А в современных компьютерах есть многозадачность аля множество программ могут выполняться на одном ядре. Как этого достигают?
На самом деле стек, значения счетчика инструкций и регистров полностью определяют состояние исполнения текущего кода. Оно называется контекстом. Если где-то сохранить это состояние, то можно загрузить PС и регистры из другого контекста и продолжить исполнение уже другой программы, а исполнение изначальной прервется. Потом, когда наступит нужный момент, исполнение изначальной программы продолжится также с помощью загрузки ее сохраненного состояния.
Так вот по сути поток - это минимальная единица исполнения кода, которой ОС выделяет кванты времени для работы. Она владеет своим стеком и характеризуется конкретными значениями счетчика инструкций и регистрами, которые составляют контекст потока. Переключение между потоками выполняет планировщик ядра, сохраняя контекст одного потока и загружая другой.
Поток в программе может быть один и может быть несколько.
Все потоки шарят между собой общую кучу, сегмент глобальных данных, код программы и прочие ресурсы процесса типа дескрипторов.
И да, стеки потоков одного процесса тоже располагаются в памяти этого процесса. Просто потоки не могут легальными способами получить доступ к стекам других потоков того же процесса, чтобы не нарушать инкапсуляцию данных. Поток пользуется данными либо из своего стека, либо из общей кучи, либо из общих глобальных объектов.
Ну и совсем коротко о том, как явно в программе создать поток.
В С++ это делается через объект std::thread.
void hello() {
std::cout << "Hello thread!\n";
}
int main() {
std::thread t(hello);
t.join();
}Ядро выделяет новый стек для потока, инициализирует его контекст так, чтобы выполнение началось с функции hello. После этого поток попадает в очередь планировщика и получает свой квант времени.
Share resources. Stay cool.
#OS #concurrency