← Назад к вопросам

За счет чего достигалась параллельность действий в старом компьютере?

2.0 Middle🔥 31 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

За счет чего достигалась параллельность в старых компьютерах

Это отличный вопрос, который показывает понимание эволюции вычислительных систем. Параллельность в старых однопроцессорных системах достигалась не тем, чем мы пользуемся сегодня.

Временное разделение (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  # Теперь не прервется

Заключение

Параллельность в старых компьютерах достигалась:

  1. Time Slicing — быстрое переключение между задачами
  2. Context Switching — сохранение и загрузка состояния
  3. Interrupts — механизм прерывания текущей задачи
  4. Hardware Timer — периодическое генерирование прерываний

Этот механизм:

  • До сих пор используется в современных ОС
  • Эмулирует параллельность на одном ядре
  • Позволил однопроцессорным системам быть многозадачными
  • Создал проблемы с синхронизацией (race conditions)
  • Привел к разработке примитивов синхронизации (мьютексы, семафоры)

Без понимания этих концепций сложно разобраться в параллельном программировании на Python (threading, asyncio, multiprocessing).