作業系統概論:行程與執行緒

當你打開電腦,同時瀏覽網頁、聽音樂、寫程式——你以為這些程式是「同時」在運作嗎?

其實,對單核心 CPU 來說,它一次只能做一件事。但它切換得非常快,讓你感覺像是同時進行。這就是 作業系統 (OS) 的核心工作之一:行程管理

🔥 Vibe Prompt

「用 Python 模擬作業系統的行程排程:實作 FIFO、SJF、Round Robin 三種演算法,每個行程有 arrival time 和 burst time,輸出完整執行順序與等待時間統計。」

什麼是行程 (Process)?

行程 就是一個正在執行的程式實例。當你雙擊 Chrome 圖示時,作業系統會:

  1. 將程式的程式碼從硬碟載入到記憶體
  2. 分配一個獨立的記憶體空間
  3. 建立一個「行程控制塊 (PCB)」來記錄它的狀態

每個行程擁有獨立的記憶體空間,彼此不會互相干擾。

import os
import time

print(f"目前行程 ID (PID): {os.getpid()}")
print(f"父行程 ID (PPID): {os.getppid()}")

行程 vs 執行緒 (Thread)

| 特性 | 行程 (Process) | 執行緒 (Thread) | |------|---------------|----------------| | 記憶體空間 | 獨立(隔離) | 共享同一行程的記憶體 | | 建立成本 | 高(需要完整複製空間) | 低(輕量級) | | 通訊方式 | IPC (pipe, socket) | 直接讀寫共享變數 | | 穩定性 | 一個掛了不影響其他 | 一個掛了可能整組崩潰 | | 使用場景 | 隔離性要求高的應用 | 需要高效率並行的任務 |

生活比喻

  • 行程 = 不同的餐廳。每間餐廳有自己的廚房、食材、員工。一家餐廳火災不會影響隔壁。
  • 執行緒 = 同一間餐廳裡的多個廚師。他們共用同一個廚房、食材庫存。協作效率高,但搞砸了會一起遭殃。
import threading
import time

shared_counter = 0
lock = threading.Lock()

def chef_task(chef_id):
    global shared_counter
    for _ in range(5):
        with lock:
            shared_counter += 1
            print(f"👨‍🍳 廚師 {chef_id} 做了第 {shared_counter} 道菜")
        time.sleep(0.1)

# 建立 3 個執行緒(廚師)
threads = []
for i in range(3):
    t = threading.Thread(target=chef_task, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(f"\n✅ 總共完成 {shared_counter} 道菜")

CPU 排程演算法

當多個行程都在等待執行時,作業系統需要決定誰先誰後。這就是 CPU 排程。

1. FIFO (First In, First Out)

先來的先執行,非常公平,但短任務會被長任務阻塞。

def fifo_scheduling(processes):
    """
    FIFO 排程
    processes: [(name, arrival_time, burst_time), ...]
    """
    processes.sort(key=lambda p: p[1])  # 依照到達時間排序
    time = 0
    schedule = []
    
    for name, arrival, burst in processes:
        if time < arrival:
            time = arrival
        schedule.append((name, time, time + burst))
        time += burst
    
    return schedule

# 測試
processes = [
    ("P1", 0, 5),
    ("P2", 1, 3),
    ("P3", 2, 8),
    ("P4", 3, 2),
]

result = fifo_scheduling(processes)
for name, start, end in result:
    print(f"{name}: {start}~{end} (執行 {end-start}ms)")

2. SJF (Shortest Job First)

最短的任務先執行,能有效減少平均等待時間。

def sjf_scheduling(processes):
    """
    SJF 排程:最短作業優先
    """
    processes = sorted(processes, key=lambda p: p[1])  # 依 arrival time
    n = len(processes)
    completed = [False] * n
    time = 0
    schedule = []
    
    for _ in range(n):
        # 找出已到達且 burst time 最短的行程
        available = [(i, p) for i, p in enumerate(processes) 
                     if not completed[i] and p[1] <= time]
        if not available:
            time += 1
            continue
        
        idx = min(available, key=lambda x: x[0][2])[0]  # 最短 burst
        name, arrival, burst = processes[idx]
        schedule.append((name, time, time + burst))
        time += burst
        completed[idx] = True
    
    return schedule

result = sjf_scheduling(processes)
for name, start, end in result:
    print(f"{name}: {start}~{end} (執行 {end-start}ms)")

3. Round Robin (時間片輪轉)

每個行程輪流執行一個固定的「時間片 (time quantum)」。

def round_robin(processes, time_quantum=2):
    """
    Round Robin 排程
    """
    queue = []
    remaining = {p[0]: p[2] for p in processes}  # 剩餘執行時間
    arrival_map = {p[0]: p[1] for p in processes}
    
    time = 0
    schedule = []
    ready_queue = []
    
    # 初始載入已到達行程
    for p in processes:
        if p[1] <= time:
            ready_queue.append(p[0])
    
    while remaining:
        if not ready_queue:
            time += 1
            # 檢查新到達的行程
            for name, arrival, _ in processes:
                if arrival == time and name in remaining:
                    ready_queue.append(name)
            continue
        
        name = ready_queue.pop(0)
        execute = min(time_quantum, remaining[name])
        schedule.append((name, time, time + execute))
        time += execute
        remaining[name] -= execute
        
        if remaining[name] <= 0:
            del remaining[name]
        else:
            ready_queue.append(name)
        
        # 加入新到達的行程
        for p_name, arrival, _ in processes:
            if p_name in remaining and p_name not in ready_queue:
                start_time = schedule[-1][2]
                if start_time < arrival <= time:
                    ready_queue.append(p_name)
    
    return schedule

result = round_robin(processes, time_quantum=2)
for name, start, end in result:
    print(f"{name}: {start}~{end}")

行程狀態轉換

一個行程在生命週期中會經歷以下狀態:

New → Ready → Running → Terminated
           ↕          ↓
        Waiting  ←  I/O 或事件等待
  • New: 剛被建立
  • Ready: 準備好執行,等待 CPU 分配
  • Running: 正在 CPU 上執行
  • Waiting: 等待 I/O 或某個事件
  • Terminated: 執行完畢

上下文切換 (Context Switch)

當 CPU 從一個行程切換到另一個行程時,必須:

  1. 儲存目前行程的狀態(暫存器、程式計數器)
  2. 載入新行程的狀態

這個動作稱為 上下文切換。每次切換約需 1~100 µs,如果切換太頻繁,CPU 會花太多時間在切換而不是真正執行任務。

總結

| 概念 | 重點 | |------|------| | 行程 (Process) | 獨立記憶體空間,重量級,適合隔離性高的任務 | | 執行緒 (Thread) | 共享記憶體,輕量級,適合並行計算 | | FIFO | 先來先做,簡單但短任務可能被阻塞 | | SJF | 最短先做,平均等待最少,但可能造成飢餓 | | Round Robin | 輪流執行,互動性最好,時間片大小是關鍵 |

實戰練習

💡 Vibe Coding 練習:請 AI 幫你實作一個「互動式行程排程模擬器」,滿足以下需求:

  1. 使用者可以輸入多個行程的名稱、到達時間、執行時間
  2. 選擇 FIFO / SJF / Round Robin 三種演算法
  3. 輸出甘特圖 (Gantt Chart) 顯示執行順序
  4. 計算並比較平均等待時間與平均周轉時間
  5. 用 HTML + CSS 做一個漂亮的視覺化介面

會員專屬免費教學

本章節為註冊會員專屬的免費開放內容!請先登入或註冊會員,即可立即解鎖閱讀。

立即登入 / 註冊