前言

今天讓我們選一門後端語言來開發 API,會怎麼選呢?PHP、Python、Node.js、Java、Golang——反正後端都是寫 API,那為什麼不同語言的薪資天花板差這麼多?本文將從「併發」的角度切入,帶你理解語言選擇背後的技術邏輯與產業現實!


情境思考:Foodpanda 的後端會選什麼語言?

思考一下,如果今天要開發一個像 Foodpanda、Uber Eats 這樣的外賣平台,你會選擇什麼語言來寫後端 API?

想像一下這個場景:

1
2
3
4
5
午餐尖峰時段 12:00~13:00:
→ 10,000 個用戶同時打開 App 瀏覽餐廳
→ 3,000 個用戶同時下單
→ 每筆訂單需要:寫入資料庫 + 通知餐廳 + 計算配送路線 + 推播給外送員
→ 你的後端撐得住嗎? 🤔

帶著這個問題,我們來看看每個語言的「體質」到底差在哪裡。


五大後端語言併發總覽

在深入每個語言之前,先用一張表格快速理解全貌。併發決定了語言如何處理「同時間大量請求」,這是影響系統效能最核心的因素。

先釐清兩組常被混淆的概念:

概念定義
同步(Synchronous)呼叫方發出請求後,必須等結果回來才能繼續往下執行
異步(Asynchronous)呼叫方發出請求後,不需要等結果,結果透過 callback / event / Promise 通知
阻塞(Blocking)等待期間,Thread 被佔住,無法做其他事
非阻塞(Non-blocking)等待期間,Thread 不被佔住,可以去處理其他任務

本文後續會分別用「I/O 模型」來描述語言在 I/O 等待時的行為(阻塞/非阻塞),用「異步能力」來描述語言是否提供異步程式設計方案(async/await、callback 等),避免混用。

項目PHPPythonNode.jsJavaGolang
執行方式直譯(8.0+ 支援 JIT)直譯(CPython Bytecode)JIT 編譯(V8)JIT 編譯(Bytecode + JVM)靜態編譯(AOT)
型別動態弱型別動態強型別動態弱型別靜態強型別靜態強型別
併發Process(FPM Worker)Thread(受 GIL 限制)Event Loop(單 Thread)ThreadGoroutine
I/O 模型同步阻塞(FPM)同步阻塞(預設)/異步非阻塞(asyncio)異步非阻塞(Event Loop)同步阻塞(預設)/異步非阻塞(NIO、CompletableFuture)同步寫法 + runtime 非阻塞 I/O(Goroutine 遇 I/O 自動讓出 Thread)
異步程式設計❌ 原生不支援(Swoole 等擴展可支援)✅ asyncio(async/await)✅ 原生(callback / Promise / async-await)✅ CompletableFuture、Reactive Streams❌ 無 async/await;透過 Goroutine + Channel 實現併發(非異步模型)
CPU 密集痛點傳統 FPM 模式下,Process 間無法共享記憶體協作,無法多核並行處理同一任務GIL 限制,無法多 Thread 並行單 Thread 阻塞 Event Loop無瓶頸,多 Thread 並行無瓶頸,多核直接並行
高併發痛點每個 Request 一個 Process,記憶體消耗大CPU 密集場景受 GIL 限制(I/O 密集可用 asyncio 緩解)單 Thread 處理能力有上限傳統 Thread 資源重(Virtual Thread 已解決)Goroutine 輕量,幾乎無痛點
記憶體消耗20~50 MB / Process~8 MB / Thread(虛擬記憶體保留量,實際物理佔用較小)低(Event Loop 共享)~200 MB JVM 一次性開銷 + 每 Thread stack 512KB~1MB(虛擬記憶體保留量)初始 2 KB / Goroutine(動態增長)

名詞補充:

  • AOT(Ahead-of-Time Compilation,提前編譯):在程式執行前就把原始碼編譯成機器碼,執行時 CPU 直接跑,啟動快、效能穩定。例如 Golang、C。
  • JIT(Just-in-Time Compilation,即時編譯):在程式執行時才動態將程式碼編譯成機器碼,可以根據實際執行狀況做最佳化,但會有啟動預熱時間。例如 Java(JVM)、Node.js(V8)。

各語言併發詳解

PHP:一個 Request 一個 Process

核心模型: PHP-FPM(FastCGI Process Manager),每個 Request 都會分配一個獨立的 Worker Process。

項目說明
執行方式直譯(8.0+ 支援 OPcache JIT)
型別動態弱型別
併發Process(PHP-FPM Worker,每個 Worker 獨立 Process)
I/O 模型同步阻塞(FPM 每個 Worker 在 I/O 等待時 Thread 被佔住)
異步能力原生不支援;Swoole / Parallel 等擴展可提供異步或協程機制
記憶體20~50 MB / Process(Laravel 框架需載入整個框架體系,更加消耗記憶體)

PHP 的請求處理流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
【PHP-FPM 處理流程】

Request 進入


┌─────────────┐
│ PHP-FPM │
│ Master │ ← 管理所有 Worker Process
└──────┬──────┘
│ 分配一個空閒 Worker

┌─────────────┐
│ Worker 1 │ ← 獨立 Process(20~50 MB)
│ 處理 Request│
│ 查詢 DB... │ ← 阻塞等待中,什麼都不能做 ⏳
│ 回傳結果 │
│ 等待下一個 │ ← Worker 回到 Pool,不會銷毀
└─────────────┘

→ Worker 處理完後回到 Pool 等待下一個 Request
→ 但同一時間每個 Worker 只能處理一個 Request!
→ 達到 pm.max_requests 上限後才會回收重建

這會導致什麼問題?

1
2
3
4
5
6
7
8
9
10
假設 1 台 Server 配置 10 個 PHP-FPM Worker:

每個 Worker 佔用:~40 MB
10 個 Worker 佔用:~400 MB

如果同時有 100 個 Request 進來:
→ 只有 10 個能被處理
→ 其餘 90 個排隊等待 ⏳
→ 若每個 Request 需要 2 秒(含 DB 查詢等 I/O 等待)
→ 第 100 個 Request 最長需等待 20 秒!

補充:PHP 也有異步方案!

  • PHP 8.0+:支援 Parallel 擴展(需 ZTS 版本 PHP,生產環境較少使用),可針對需要的部分(外部請求、CPU 密集運算等)使用多 Thread 解決阻塞
  • Laravel Octane:基於 SwooleRoadRunner,導入 Coroutine 機制提升併發能力
  • Hyperf:基於 Swoole 的協程框架,原生支援高併發

但這些方案本質上是「PHP」追加的併發改善方案,而非語言原生支援,學習成本也較高。所以大多數企業會認為,既然這麼麻煩,一開始就選擇不會有這些瓶頸的語言,那麼就不需要考慮甚至進行大量評估了。

優點:開發速度快、生態成熟、學習門檻低

缺點:每個 Request 獨立 Process,記憶體消耗大、高併發承受力差

Python:被 GIL 鎖住的多執行緒

核心模型: 多 Thread,但受 GIL(Global Interpreter Lock)限制,同一時間只有 1 個 Thread 能執行 Python 程式碼。(⚠️ 重要:所以 Python 本質並沒有「並行處理」而是「併發處理」)

項目說明
執行方式直譯(CPython Bytecode)
型別動態強型別
併發Thread(受 GIL 限制);也可用 multiprocessing 開多 Process 繞過 GIL
I/O 模型預設同步阻塞;使用 asyncio 時為異步非阻塞(I/O 密集場景表現優秀)
異步能力asyncio(async/await),適用於 I/O 密集場景
記憶體~8 MB / Thread(Linux 預設 stack 保留量,實際物理記憶體佔用較小)

什麼是 GIL?為什麼它是瓶頸?

GIL(Global Interpreter Lock) 是 CPython 直譯器的全域鎖,它限制同一時間只能有 1 個 Thread 執行 Python 的程式碼,目的是避免 Race Condition(競爭條件)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
【GIL 的影響】

假設有 4 個 Thread 要處理 4 個 CPU 密集任務:

理想情況(無 GIL):
Thread 1: ████████ → 同時執行
Thread 2: ████████ → 同時執行
Thread 3: ████████ → 同時執行
Thread 4: ████████ → 同時執行
時間: ========(1x)

實際情況(有 GIL):
Thread 1: ██ ██ ██ ██
Thread 2: ██ ██ ██ ██
Thread 3: ██ ██ ██ ██
Thread 4: ██ ██ ██
時間: ================================(≈ 4x)

→ 有 4 個 Thread,但速度跟 1 個 Thread 差不多!

Python 3.13+ 的新進展:
從 Python 3.13 開始,官方提供了 Free-threaded CPython(實驗性),可以關閉 GIL,讓多 Thread 真正並行,這也是現在大多數程式語言主流的趨勢,那就是並行甚至異步處理,所以說 PHP 就是老古董玩意兒,但官方或者框架也逐漸在朝著「高併發」、「非阻塞 I/O」、「JIT」這類能讓語言效能更高的趨勢靠攏。

重要補充:GIL 主要影響 CPU 密集場景!
在 I/O 密集場景下(如 Web API、DB 查詢),GIL 會在等待 I/O 時釋放鎖,因此多 Thread 仍然有效。搭配 asyncio 更能高效處理大量 I/O 併發。此外也可以用 multiprocessing 開多 Process 來繞過 GIL 限制,實現真正的 CPU 並行。

優點:語法簡潔、AI/ML 生態系無人能敵、asyncio 處理 I/O 密集場景表現優秀

缺點:GIL 限制 CPU 密集並行能力(可用 multiprocessing 緩解)

Node.js:單執行緒的 Event Loop 魔法

核心模型: 單一 Thread 的 Event Loop + Worker Threads(需自行使用)。Event Loop 本身跑在一個 Thread 上,但 Node.js 內部的 libuv thread pool(預設 4 個 Thread)會負責處理檔案 I/O 等阻塞操作。所有 I/O 操作對開發者而言為非阻塞(Non-blocking),透過 callback / Promise 處理結果。

項目說明
執行方式JIT 編譯(V8 引擎)
型別動態弱型別(主流搭配 TypeScript 成為強型別)
併發Event Loop(單 Thread)+ Worker Threads
I/O 模型異步非阻塞(Event Loop 處理 I/O,透過 callback / Promise 取得結果;底層 libuv 負責非阻塞 I/O)
異步能力原生支援(callback / Promise / async-await)
記憶體低(Event Loop 共享記憶體)

Event Loop 是怎麼運作的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
【Node.js Event Loop 模型】

┌────────────────────────────┐
│ Event Loop │
│ (單 Thread) │
│ │
Request A ──────▶ │ 1. 收到 Request A │
Request B ──────▶ │ 2. 收到 Request B │
Request C ──────▶ │ 3. 收到 Request C │
│ │
│ A 需要查 DB → 丟給 I/O 層 │──▶ DB 查詢中...
│ B 需要呼叫 API → 丟給 I/O 層│──▶ API 呼叫中...
│ C 是純計算 → 直接處理 │
│ │
│ DB 回來了 → 執行 A 的 callback│
│ API 回來了 → 執行 B 的 callback│
└────────────────────────────┘

→ 不需要為每個 Request 建立 Thread/Process!
→ I/O 等待時不會閒置,繼續處理其他 Request!

單 Thread 的致命弱點:CPU 密集任務

Node.js 官方文件明確提過:「Don’t block the Event Loop.」
當 Event Loop 在處理一個 CPU 密集任務(如 O(N²) 演算法)時,所有其他 Request 都會被擋住!

1
2
3
4
5
6
7
8
9
10
11
12
【CPU 密集任務阻塞 Event Loop】

Event Loop(單 Thread):

正常情況:
A(I/O) → B(I/O) → C(I/O) → A回來 → B回來 → C回來
✅ 非常快,因為 I/O 等待時間不阻塞

CPU 密集情況:
A(I/O) → B(CPU 密集❗) → ████████████████████ → C(還在排隊...)
↑ 整個 Event Loop 被卡死
↑ Request C 只能乾等 ⏳

解法: 使用 Worker Threads 將 CPU 密集任務移到獨立 Thread 處理:

1
2
const { Worker } = require('worker_threads');
const worker = new Worker('./heavy-computation.js');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
【單一 Node.js Process 內部的 Thread 全貌】

┌─────────────────────────────────────────────────────────┐
│ Node.js Process │
│ │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ Event Loop │ │ libuv thread pool │ │
│ │ (單一 Thread) │ │ Thread 1 Thread 2 │ │
│ │ │ │ Thread 3 Thread 4 │ │
│ │ 處理: │ │ │ │
│ │ - JS 執行 │ │ 處理: │ │
│ │ - callback │◀───│ - 檔案 I/O(fs) │ │
│ │ - Promise │ │ - DNS 查詢 │ │
│ │ - 網路 I/O │ │ - crypto(部分) │ │
│ └─────────────────┘ └──────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ Worker Thread │ ← 手動建立,處理 CPU 密集任務 │
│ │(worker_threads)│ 與 Event Loop 同一 Process, │
│ │ │ 可透過 SharedArrayBuffer │
│ │ │ 共享記憶體,其餘透過 postMessage │
│ └─────────────────┘ │
│ │
│ → 所有 Thread 共享同一個 Process 的記憶體空間 │
│ → libuv thread pool 由 Node.js 自動管理,開發者無感 │
│ → Worker Thread 需手動建立,適合 CPU 密集任務 │
└─────────────────────────────────────────────────────────┘

對比:cluster 模式(PM2)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Process 1 │ │ Process 2 │ │ Process 3 │
│ Event Loop │ │ Event Loop │ │ Event Loop │
│ + libuv │ │ + libuv │ │ + libuv │
└──────────────┘ └──────────────┘ └──────────────┘
❌ Process 之間不共享記憶體,各自獨立
✅ 可利用多核 CPU,每個 Process 跑一顆核心

補充:Node.js 生產環境通常使用 cluster 模式!
透過 cluster 模組或 PM2,可以啟動多個 Process(通常 = CPU 核心數),每個 Process 擁有獨立的 Event Loop,藉此利用多核 CPU。所以 Node.js 並非只能用單核,只是單個 Process 內的 Event Loop 是單 Thread

優點:I/O 密集場景效能極佳、前後端語言統一、npm 生態龐大

優點:搭配 cluster / PM2 可利用多核 CPU

缺點:單一 Event Loop 無法處理 CPU 密集任務(需 Worker Threads)、cluster 多 Process 間不共享記憶體

Java:企業級的多執行緒戰車

核心模型: 每個請求分配一個 Thread。Java 21+ 引入 Virtual Thread(虛擬執行緒),由 JVM 管理,多個 Virtual Thread 共享少量底層 Thread,大幅降低資源消耗。

項目說明
執行方式JIT 編譯(Bytecode + JVM)
型別靜態強型別
併發Thread(Java 21+ 支援 Virtual Thread)
I/O 模型預設同步阻塞;NIO 提供非阻塞 I/O;Virtual Thread 為同步阻塞寫法但 JVM 自動讓出底層 Thread
異步能力CompletableFuture(異步)、Reactive Streams(異步非阻塞)
記憶體JVM 基礎開銷 ~200 MB(Spring Boot),但屬一次性開銷

Java Thread 模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
【傳統 Thread(Java 20 以前)】

Request 1 ──▶ Thread 1(stack 預設 ~512KB-1MB) ──▶ 處理完畢 → 釋放
Request 2 ──▶ Thread 2(stack 預設 ~512KB-1MB) ──▶ 處理完畢 → 釋放
Request 3 ──▶ Thread 3(stack 預設 ~512KB-1MB) ──▶ 處理完畢 → 釋放
...
Request 1000 ──▶ Thread 1000 ❌ 記憶體不足!

→ 傳統 Thread stack 預設 512KB~1MB(虛擬記憶體保留量,同 Python Thread)
→ 加上 OS 層面的 Thread 管理開銷,1000 個 Thread 實際記憶體消耗仍相當可觀


【Virtual Thread(Java 21+)】

Request 1 ──▶ Virtual Thread 1(~幾 KB)
Request 2 ──▶ Virtual Thread 2(~幾 KB) ── 由 JVM 調度 ──▶ 少量 Thread
Request 3 ──▶ Virtual Thread 3(~幾 KB)
...
Request 100,000 ──▶ Virtual Thread 100,000 ✅ 輕鬆處理!

→ Virtual Thread 由 JVM 管理,不直接對應 Thread
→ 資源消耗極低,可輕鬆建立數十萬個

Java 21 的 Virtual Thread 是一個里程碑式的更新,讓 Java 在高併發場景下的資源消耗大幅降低,直接對標 Golang 的 Goroutine。

注意:Virtual Thread 不是異步方案,而是「同步寫法 + 非阻塞底層」!
Virtual Thread 的設計哲學跟 Goroutine 一樣——開發者寫的是同步阻塞式程式碼,但當 Virtual Thread 遇到 I/O 等待時,JVM 會自動將它從底層 OS Thread 上卸載(unmount),讓該 OS Thread 去執行其他 Virtual Thread,達到非阻塞的效果。

換句話說:

  • 從呼叫方(開發者)角度看:是同步的(寫法上一行一行等結果)
  • 從 Thread 資源角度看:是非阻塞的(OS Thread 不會被佔住空等)

Java 真正的異步方案是 CompletableFutureReactive Streams(Project Reactor / RxJava)——這些需要用 callback / 響應式寫法,呼叫後不等結果。Virtual Thread 則屬於併發方案,讓你用簡單的同步寫法就能享受非阻塞的好處。

優點:真正多 Thread 並行、JVM 生態成熟、靜態強型別穩定可靠

優點:Virtual Thread(Java 21+)解決了傳統 Thread 資源消耗大的問題

缺點:JVM 基礎開銷大(~200 MB)、Spring Boot 啟動慢、開發門檻較高

Golang:天生為併發而生

核心模型: Goroutine + M:N Scheduler。Go runtime 自動將輕量級的 Goroutine 分配到多個 Thread,實現真正的多核並行。

項目說明
執行方式靜態編譯(AOT)
型別靜態強型別
併發Goroutine(M:N Scheduler)
I/O 模型同步寫法 + runtime 非阻塞(開發者寫同步程式碼,Goroutine 遇 I/O 時 runtime 自動讓出 Thread 給其他 Goroutine)
異步能力無 async/await;透過 go 關鍵字啟動 Goroutine + Channel 通訊實現併發,而非異步程式設計模型
記憶體極低(初始 2 KB / Goroutine,動態增長)

程式碼 Demo:Goroutine 併發請求外部 API

Go 沒有 async/await,但這不代表要一個一個等!用 go 關鍵字啟動 Goroutine,讓其實作併發,參考如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
"fmt"
"net/http"
"sync"
"time"
)

// 模擬呼叫外部 API(例如:餐廳服務、付款服務、推播服務...)
func callAPI(name string, wg *sync.WaitGroup) {
defer wg.Done() // 完成後通知 WaitGroup

start := time.Now()

// ⭐ 這裡看起來是「同步阻塞」的寫法
// 但 Go runtime 底層會自動處理成非阻塞 I/O
// 當這個 Goroutine 在等待回應時,scheduler 會切換去執行其他 Goroutine
resp, err := http.Get("https://httpbin.org/delay/1")
if err != nil {
fmt.Printf("❌ %s 請求失敗: %v\n", name, err)
return
}
defer resp.Body.Close()

fmt.Printf("✅ %s 回應完成,耗時: %v\n", name, time.Since(start))
}

func main() {
apis := []string{
"餐廳服務",
"付款服務",
"推播服務",
"庫存服務",
"物流服務",
}

var wg sync.WaitGroup
start := time.Now()

for _, api := range apis {
wg.Add(1)
go callAPI(api, &wg) // ⭐ go 關鍵字 → 啟動一個 Goroutine,併發執行!
}

wg.Wait() // 等待所有 Goroutine 完成
fmt.Printf("\n🎯 全部完成!總耗時: %v(不是 5 秒,而是 ~1 秒)\n", time.Since(start))
}
1
2
3
4
5
6
7
8
9
10
11
12
執行結果:

✅ 推播服務 回應完成,耗時: 1.05s
✅ 付款服務 回應完成,耗時: 1.06s
✅ 餐廳服務 回應完成,耗時: 1.07s
✅ 庫存服務 回應完成,耗時: 1.08s
✅ 物流服務 回應完成,耗時: 1.09s

🎯 全部完成!總耗時: 1.09s(不是 5 秒,而是 ~1 秒)

→ 5 個 API 各需 1 秒,但併發執行後總耗時只有 ~1 秒 ✅
→ 如果是串行(一個等一個),總耗時會是 5 秒 ❌

重點整理: 不需要 async、不需要 await、不需要 Promise,只要一個 go 關鍵字,Go runtime 就會自動幫你把 I/O 等待變成非阻塞,並在等待期間去執行其他 Goroutine 💪


Goroutine 的 M:N 調度模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
【Goroutine M:N Scheduler】

Goroutine 層(用戶態,輕量級)
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ G1 │ │ G2 │ │ G3 │ │ G4 │ │ G5 │ ← 數十萬個 Goroutine(初始各 2 KB,動態增長)
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
│ │ │ │ │
└────┬───┴────┬───┴────────┴───┬────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Thread│ │ Thread│ │ Thread│ ← 少量 Thread(通常 = CPU 核心數)
│ (M1) │ │ (M2) │ │ (M3) │
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────┐
│ 多核 CPU │
└──────────────────────────────────────┘

→ Go runtime 自動把 Goroutine 分配到 Thread
→ 某個 Goroutine 遇到 I/O 等待時,scheduler 自動切換到其他 Goroutine
→ Channel 用於 Goroutine 之間的安全通訊與同步

記憶體消耗比較

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
同時處理 100,000 個請求時,各語言的應對方式:

PHP(FPM):
pm.max_children 通常設 50~500,超出的請求直接排隊等待
→ 500 Worker × 40 MB = 20 GB,但同時只能處理 500 個請求
→ 100,000 個請求需排隊依次處理 ❌ 延遲極高

Python(Thread):
100,000 Thread × 8 MB(stack 保留)≈ 781 GB 虛擬記憶體
→ OS 無法建立這麼多 Thread ❌ 不可能

Java(Thread):
100,000 Thread × 512KB~1MB ≈ 48~97 GB
→ 理論可行但實際受 OS 限制 ❌ 很困難

Java(Virtual Thread, 21+):
100,000 Virtual Thread × 幾 KB ≈ 數百 MB
→ JVM 自動調度到少量 Thread ✅ 可行

Golang(Goroutine):
100,000 Goroutine × 初始 2 KB = 200 MB(實際會動態增長,但仍遠低於其他方案)
→ Go runtime M:N 調度 ✅ 輕鬆

優點:Goroutine 極輕量、原生支援無痛點、編譯為原生機器碼效能極佳

優點:語法簡潔、內建格式化工具、部署簡單(單一二進位檔)

缺點:生態系相較 Java/Python 較小、泛型支援較晚(Go 1.18+)、錯誤處理冗長


回到情境:Foodpanda 應該選什麼語言?

以 Foodpanda 這類需要高併發 + 非阻塞 I/O(避免 Thread 空等浪費資源) + CPU 密集運算的後端來說,答案已經非常明顯了。

1
2
3
4
5
6
7
8
9
10
11
12
13
Foodpanda 後端需求分析:

┌──────────────────┬────────────────────────────────────────┐
│ 需求 │ 說明 │
├──────────────────┼────────────────────────────────────────┤
│ 高併發 │ 午餐尖峰數萬用戶同時下單 │
│ 併發處理 │ 下單後需同時通知餐廳、計算路線、推播外送員 │
│ CPU 密集 │ 配送路線計算、推薦演算法、即時定價 │
│ 高可用性 │ 掛了 = 沒收入,必須穩定運行 │
│ 快速回應 │ 用戶下單後需在毫秒級得到回應 │
└──────────────────┴────────────────────────────────────────┘

→ Java 與 Golang 必然是首選 ✅

為什麼 Java / Golang 是首選?

需求PHPPythonNode.jsJavaGolang
高併發❌ Process 太重⚠️ CPU 受 GIL 限制(I/O 可用 asyncio)✅ Event Loop 擅長 I/O 併發✅ Virtual Thread✅ Goroutine
CPU 密集❌ Process 間無法協作並行❌ GIL 限制並行❌ 阻塞 Event Loop✅ 多核並行✅ 多核並行
I/O 模型❌ 同步阻塞✅ asyncio(異步非阻塞)✅ Event Loop(異步非阻塞)✅ Virtual Thread(同步寫法,底層非阻塞)/ NIO✅ Goroutine(同步寫法,runtime 非阻塞)
系統穩定性⚠️ 動態弱型別⚠️ 動態型別⚠️ 動態弱型別✅ 靜態強型別✅ 靜態強型別
運行效能⚠️ 直譯(8.0+ JIT 有提升)❌ 直譯最慢⚠️ V8 JIT 尚可✅ JIT 編譯✅ 原生編譯最快

Java 和 Golang 對於複雜的業務場景,由於本身原生就支援強大的併發能力,後續有任何需要調整的情況也能更好的應付,而不需要像 PHP 考慮是不是要導入 Swoole 一一儘管導入 Swoole 其實主要也是把 Coroutine 拿來做使用,而非完全利用 Threads。


語言選擇如何影響薪資?

記住我講的這句話(鎖定:為了避免過於混亂,這邊鎖定台灣內的所有台灣公司):
「寫 Golang 不破百都對不起自己,寫 PHP 能破百都要謝天謝地。」

那麼為什麼不同語言的薪資天花板差這麼多呢?

我認為你選擇的「語言」會決定你公司的「產業」類型,而當你的「產業」不賺錢,那麼這份職缺自然薪水不高。

舉個簡單例子:你在出版業做軟體工程師,跟在科技業做軟體工程師,同樣都輕鬆躺平弄弄日常維運,準時上下班,在累積一定年資後,後者科技產業的薪資成長性會遠遠超過出版業一一

所以台灣新聞我從來沒看過出版業薪水年終多少,幾乎都在報科技業年終多少。


獲利能力強的公司 → 用什麼語言?

第一種:客單價低但用戶量龐大

客單價低甚至免費,但擁有龐大用戶群體。因為規模化,可以做市場、賣廣告,甚至把每個免費用戶變成產品,例如 Google、Meta、Line 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
【這類公司的技術需求】

龐大用戶量


需要強大的負載能力

├─ 處理「高併發」:數百萬用戶同時在線
├─ 處理「CPU 密集」:推薦演算法、搜尋排序、即時計算
└─ 處理「低延遲」:回應時間需在毫秒級


首選語言:Java、Golang ✅

語言需求:高併發 + CPU 密集 + 低延遲 → Java / Golang

薪資特點:技術門檻高、公司獲利強 → 薪資天花板高

第二種:客單價高但用戶群體小

每個客單價高,所以也有足夠的收益,例如客製化的 B2B 產品、金融產品、企業 ERP 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
【這類公司的技術需求】

高客單價


品質必須穩固(花大錢買的產品不能當機)

├─ 需要「靜態強型別」:減少低級錯誤、提升程式碼品質
├─ 需要「高併發」:產品遇到流量高峰不能停機
└─ 需要「穩定可靠」:不能動不動就 Bug


首選語言:Java、Golang、C# ✅

想像一下你花大錢買了一台蘋果手機,結果動不動就熱當機,那很顯然這個產品很快就會被淘汰。「靜態強型別」等穩健的語言自然就是首選。

語言需求:穩定可靠 + 高品質 → 靜態強型別語言

薪資特點:產品價值高、品質要求嚴格 → 薪資天花板高

那 PHP、Python、Node.js 呢?

這三個語言由於語言特性(直譯 + 動態型別),可以更快地進行開發,主要運用在需要快速迭代的場景。

場景說明常見語言
MVP 快速驗證先做出來搶市場,品質其次PHP、Node.js、Python
接案公司to B / to G 的客製化專案PHP(主流)
成長期企業快速迭代、搶佔市場Node.js、Python
AI / ML 領域模型訓練、數據分析Python(無可取代)

不能說完全沒有高薪,例如在成長期的企業中快速做出 MVP,這些語言也能拿到不錯的薪資。但重點在於出成果、搶市場,品質並非最優先(不能有重大 Bug,但允許小 Bug)。

以 PHP 來說,這門語言大多常用在接案公司,可能是接 to B、to G 的案子。to C 的產品也有,但如果規模到了一定程度,就會開始逐漸重構,甚至把 API 轉移到 Golang 等語言上了。

優點:開發速度快、學習門檻低、生態豐富、轉職好上岸

缺點:產業天花板限制薪資成長性


併發比較一圖流

五大語言高併發處理示意圖(TW)

五大言語高並行処理の概念図(JP)


總結

語言選擇的核心邏輯: 語言 → 決定能力 → 決定產業 → 決定薪資天花板。
選擇語言不只是技術問題,更是職涯策略問題!(未來有機會再寫幾篇職涯策略選擇的文章!😈)

(以下只是我個人主觀的評論,僅供參考)

語言併發適合場景薪資天花板(台灣)
PHPProcess(FPM)接案、CMS、MVP 快速開發⭐⭐
Node.jsEvent LoopI/O 密集 API、即時通訊、全端開發⭐⭐⭐
PythonThread(GIL)AI/ML、數據分析、腳本自動化⭐⭐⭐⭐(AI 領域高薪)
JavaThread / Virtual Thread大型企業系統、金融、電商平台⭐⭐⭐⭐✨
GolangGoroutine高併發微服務、雲原生、基礎設施⭐⭐⭐⭐⭐

快速決策表

你的情況推薦語言一句話理由
剛入行,想快速找到工作PHP / Node.js / Python學習門檻低、職缺多
想做 AI / 數據分析Python當前生態系難以取代的霸主
想進大型或者高流量產品公司Java / Golang高併發場景必備技能
享受躺著年資上去就能高薪Golang區塊鏈、高併發,所以有錢
想全端通吃Node.js + TypeScript / PHP前後端語言統一、包山包海 PHP
已有高流量產品要重構Golang / Java穩定 + 高效能

延伸閱讀

各語言併發官方文件:

相關文章: