100 執行緒各對全域 g_iTestInteger 執行 ++,主執行緒列印結果。預期 100,實際輸出 98~99 不定,且多次執行結果不同。
++ 非原子操作g_iTestInteger++ 編譯為三條指令:
LOAD 從記憶體讀取值到暫存器。INC 暫存器加 1。STORE 寫回記憶體。兩執行緒同時 LOAD 同一值 (如 5),各自 INC 為 6,先後 STORE 6,導致少算 1。此為資料競爭。
執行緒內列印計數值,發現序號亂序、主執行緒列印時仍有子執行緒在跑 (輸出 98 後仍出現 99, 100, 74...)。
| 方案 | 原理 | 優點 | 缺點 |
|---|---|---|---|
主執行緒 sleep 等待 | 時間換空間 | 簡單 | 不可靠、浪費 CPU、無法確定等多久 |
pthread_join 回收執行緒 | 阻塞等待特定執行緒結束 | 標準、精確、無競爭 | 需儲存所有 thread_id |
互斥鎖 pthread_mutex_lock | 序列化關鍵區 | 通用、精確控制 | 效能開銷、死鎖風險 |
原子操作 __sync_fetch_and_add / C11 atomic_fetch_add | 硬體級原子指令 | 無鎖、高效能 | 需編譯器/硬體支援、僅限簡單操作 |
#include#define THREAD_NUM 100 uint32_t g_iTestInteger = 0; void *ProcessTask(void *arg) { // 方案 A: 互斥鎖 (需全域 mutex 初始化) // pthread_mutex_lock(&mutex); // g_iTestInteger++; // pthread_mutex_unlock(&mutex); // 方案 B: 原子操作 (GCC/Clang 內建) __sync_fetch_and_add(&g_iTestInteger, 1); return NULL; } int main() { pthread_t tids[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) pthread_create(&tids[i], NULL, ProcessTask, NULL); for (int i = 0; i < THREAD_NUM; i++) pthread_join(tids[i], NULL); // 等待所有結束 printf("In main, TestInteger = %u\n", g_iTestInteger); // 穩定輸出 100 return 0; }
pthread_join 是等待執行緒結束的正規手段,sleep 是反模式。