Первые компьютеры работали без операционных систем. Они выполняли единственную программу от начала до конца, которая имела прямой доступ ко всем ресурсам машины. Создание и выполнение таких программ требовало дорогостоящих и дефицитных ресурсов.
Со временем операционные системы эволюционировали, позволив выполнять несколько программ одновременно в рамках процессов (processes) — изолированных, независимо выполняемых программ, которые пользуются ресурсами операционной системы, такими как память, дескрипторы файлов и учетные данные системы безопасности. При необходимости процессы могут взаимодействовать друг с другом с помощью коммуникационных механизмов: сокетов, обработчиков сигналов, совместной памяти, семафоров и файлов.
Перемены произошли по нескольким причинам:
Задействованность ресурсов. Время ожидания внешних операций (ввода и вывода), когда программы не делают никакой полезной работы, удобно использовать для запуска других программ.
Справедливость. Разные пользователи и программы могут иметь одинаковые права на машинные ресурсы и должны использовать компьютер совместно, не дожидаясь полного завершения одной программы перед запуском другой.
Удобство. Зачастую проще написать несколько программ, каждая из которых выполняет одну задачу, и координировать их друг с другом, чем написать единственную программу, выполняющую все задачи.
В ранних системах с режимом разделения времени каждый процесс напоминал работу компьютера фон Неймана: он хранил в памяти инструкции и данные, которые последовательно выполнял в соответствии с семантикой машинного языка, взаимодействуя с внешним миром через набор примитивов ввода-вывода. Для каждой выполняемой инструкции была четко определена следующая, и управление соответствовало правилам набора инструкций. Почти все широко используемые сегодня языки программирования следуют этой модели, определяющей их спецификацию.
Последовательное программирование интуитивно понятно: человек решает одну задачу за один раз, выстраивает очередность действий. Встать с кровати, накинуть халат, заварить чай. Как и в программировании, любое из этих реальных действий (например заварить чай) является абстракцией для другой последовательности: открыть шкаф, выбрать чай, насыпать его в чайник, налить необходимое количество воды, поставить чайник на плиту, включить плиту, дождаться, когда закипит вода, и т. д. Некоторые шаги предусматривают асинхронность (asynchrony) — пока вода нагревается, вы можете, например, запустить тостер. Производители чайников и тостеров знают, что их изделия часто используются асинхронно, поэтому снабжают их звуковым сигналом завершения задачи. Нахождение баланса между последовательностью и асинхронностью является критерием эффективности как для человека, так и для программы.
Проблемы задействованности ресурсов, справедливости и удобства способствовали развитию не только процессов, но и потоков выполнения (threads), которые позволяют многочисленным программным потокам данных сосуществовать внутри процесса. Совместно используя общепроцессные ресурсы, каждый поток выполнения имеет свой программный счетчик, стек и локальные переменные. Потоки обеспечивают естественную декомпозицию для задействования аппаратного параллелизма в многопроцессорных системах и внутри одной программы могут действовать одновременно в нескольких процессорах.
Потоки иногда называют облегченными процессами (lightweight processes), и в большинстве современных операционных систем именно они считаются основными единицами планирования. При отсутствии явной координации потоки выполняются одновременно и асинхронно по отношению друг к другу. Имея равный доступ к адресному пространству памяти процесса, все потоки используют одинаковые переменные и объекты из одной кучи, благодаря чему работают точнее межпроцессных механизмов. Но без явной синхронизации, обеспечивающей координацию доступа к совместным данным, потоки могут изменять переменные, которые уже используются другими потоками, с непредсказуемым результатом.