За счет чего достигалась параллельность действий в старом компьютере?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
За счет чего достигалась параллельность в старых компьютерах
Это отличный вопрос, который показывает понимание эволюции вычислительных систем. Параллельность в старых однопроцессорных системах достигалась не тем, чем мы пользуемся сегодня.
Временное разделение (Time Slicing) / Multitasking
Основной механизм в однопроцессорных системах
В старых компьютерах с одним ядром параллельность достигалась за счет быстрого переключения между задачами:
# Концептуально это работает как-то так:
class CPUScheduler:
def __init__(self):
self.tasks = [] # Очередь задач
self.time_slice = 10 # миллисекунд
def run(self):
while self.tasks:
current_task = self.tasks.pop(0)
# Выполняем задачу в течение time_slice
start_time = time.time()
while (time.time() - start_time) < self.time_slice:
current_task.execute_one_instruction()
# Если задача не завершена, возвращаем в конец очереди
if not current_task.is_done():
self.tasks.append(current_task)
Процессор выполняет:
- Инструкцию программы А (например, 1 ms)
- Инструкцию программы Б (1 ms)
- Инструкцию программы В (1 ms)
- Вернулась к программе А
Это происходит так быстро, что пользователю кажется что все программы работают одновременно.
Контекстное переключение (Context Switching)
Как именно процессор переключается
class ProcessContext:
"""Состояние процесса которое нужно сохранить при переключении"""
def __init__(self):
self.registers = {} # Значения всех регистров CPU
self.program_counter = 0 # Где сейчас выполняется программа
self.stack_pointer = 0 # Указатель на стек
self.memory_map = {} # Виртуальная память процесса
class ProcessScheduler:
def switch_context(self, from_process, to_process):
# 1. Сохраняем состояние текущего процесса
from_process.context = self.save_registers()
# 2. Выполняем переключение памяти (если используется виртуальная память)
self.switch_memory_page_tables(to_process)
# 3. Загружаем регистры нового процесса
self.load_registers(to_process.context)
# 4. Переключаем Program Counter на точку где был прерван
self.cpu.program_counter = to_process.context.program_counter
Это операция очень дорогая:
- Сохранение десятков регистров
- Инвалидация кэша CPU
- Очистка TLB (Translation Lookaside Buffer)
- Загрузка новых страниц памяти
Прерывания (Interrupts)
Механизм вынуждающий переключение
Процессор не сам решает когда переключаться. Есть несколько способов:
1. Аппаратные прерывания
Подпрограмма А работает...
↓
Поступило прерывание от жесткого диска (данные готовы)
↓
Процессор сохраняет контекст программы А
↓
Процессор выполняет обработчик прерывания (Interrupt Handler)
↓
Возвращается к программе А
2. Программные прерывания (System Calls)
# Когда программа хочет сделать что-то требующее привилегий
# (например, прочитать файл), она вызывает syscall
data = os.read(file_descriptor, 1024) # sys.read() system call
# Процессор:
# 1. Переходит из User Mode в Kernel Mode
# 2. Сохраняет контекст
# 3. Выполняет операцию в ядре
# 4. Возвращается обратно
3. Timer Interrupt (самое важное для многозадачности)
Timer設定на 10ms
↓
Программа А работает
↓
Прошло 10ms → Timer прерывание!
↓
ОС вмешивается → сохраняет контекст А
↓
ОС выбирает следующую программу (например Б)
↓
Загружает контекст Б
↓
Программа Б работает еще 10ms
↓
Процесс повторяется
Архитектурные компоненты
# Типичная архитектура старого ПК:
class OldComputerArchitecture:
def __init__(self):
# 1. CPU с одним ядром
self.cpu = SingleCoreCPU()
# 2. Programmable Interval Timer (PIT)
# Периодически генерирует прерывания
self.pit = Timer(interval=10) # 10ms тактика
# 3. Interrupt Controller
# Приоритизирует и маршрутизирует прерывания
self.interrupt_controller = PIController()
# 4. Scheduler в ОС
# Решает какой процесс запустить дальше
self.scheduler = ProcessScheduler()
# 5. Memory Management Unit (MMU)
# Управляет виртуальной памятью
self.mmu = MMU()
Примеры на разных системах
CP/M (1970s)
Не было многозадачности вообще!
В системе работала одна программа за раз
Опер. система передавала управление программе
Программа сама решала когда вернуть управление
MS-DOS (1980s)
Так же, однозадачная система
Когда запустили Windows - это была надстройка
Которая эмулировала многозадачность
Переключаясь между окнами каждые 50ms
UNIX / Linux (1970s-present)
Preemptive multitasking
ОС может прервать любой процесс в любой момент
Таймер прерывает каждые 10-100ms (зависит от конфига)
Scheduler выбирает следующий процесс
Почему это важно для современного Python разработчика
Этот механизм все еще используется в современных ОС:
import threading
import time
def thread_a():
for i in range(5):
print(f"A{i}")
time.sleep(0.01)
def thread_b():
for i in range(5):
print(f"B{i}")
time.sleep(0.01)
t1 = threading.Thread(target=thread_a)
t2 = threading.Thread(target=thread_b)
t1.start()
t2.start()
# Вывод может быть:
# A0 B0 A1 B1 A2 B2 ...
# или
# A0 A1 B0 A2 B1 B2 ...
#
# Потому что OS решает когда переключаться!
Проблемы параллельности через Time Slicing
Race Conditions
# Две программы обращаются к одной переменной
counter = 0
# Программа A
counter += 1 # Шаг 1: прочитать counter (=0)
# ПРЕРЫВАНИЕ! Переключение на B
# Программа B
counter += 1 # Прочитать counter (все еще =0 в памяти!)
# Прерывание, вернулись в A
# Программа A
# Шаг 2: записать counter (=1)
# Прерывание
# Программа B
# Записать counter (=1)
# Результат: counter = 1 вместо 2!
Решение - мьютексы (Mutex - Mutual Exclusion)
import threading
lock = threading.Lock()
counter = 0
def increment():
global counter
with lock: # Обычно реализуется через семафоры в ОС
counter += 1 # Теперь не прервется
Заключение
Параллельность в старых компьютерах достигалась:
- Time Slicing — быстрое переключение между задачами
- Context Switching — сохранение и загрузка состояния
- Interrupts — механизм прерывания текущей задачи
- Hardware Timer — периодическое генерирование прерываний
Этот механизм:
- До сих пор используется в современных ОС
- Эмулирует параллельность на одном ядре
- Позволил однопроцессорным системам быть многозадачными
- Создал проблемы с синхронизацией (race conditions)
- Привел к разработке примитивов синхронизации (мьютексы, семафоры)
Без понимания этих концепций сложно разобраться в параллельном программировании на Python (threading, asyncio, multiprocessing).