首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言多執行緒:全域變數累加的競態條件分析與解決方案

     編輯:關於C語言

問題現象

建立 100 個執行緒,每個執行緒對全域變數 g_iTestInteger 執行 ++ 操作。理論結果應為 100,但實際執行多次發現結果為 98~99 且不固定,甚至主執行緒列印時仍有子執行緒在執行。

根本原因:非原子操作與競態條件

g_iTestInteger++ 在機器碼層級至少包含三步:讀取 → 加一 → 寫回。多執行緒並發執行時,可能發生交錯:

  • 執行緒 A 讀取值 5
  • 執行緒 B 讀取值 5
  • A 寫回 6,B 寫回 6 → 少算一次

此外,主執行緒未等待子執行緒結束直接列印,導致結果更不可預期。

驗證實驗:加入列印觀察執行順序

在 ProcessTask 中加入 printf 後發現:執行緒執行順序亂序,且主執行緒列印時仍有子執行緒在跑。

解決方案一:主執行緒等待 (不推薦用於生產)

// 建立執行緒後 sleep(1) 等待所有執行緒結束
Sleep(1000);  // 休眠 1 秒
printf("In main, TestInteger = %d", g_iTestInteger);

結果固定為 100,但屬於「碰運氣」同步,無法保證所有平台、負載下皆正確。

解決方案二:使用互斥鎖 (pthread_mutex_t) —— 正統做法

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *ProcessTask(void *arg) {
    pthread_mutex_lock(&mutex);
    g_iTestInteger++;
    pthread_mutex_unlock(&mutex);
    return NULL;
}

解決方案三:原子操作 (C11 stdatomic.h / GCC 內建)

#include <stdatomic.h>
atomic_int g_iTestInteger = 0;

// 執行緒中直接:
atomic_fetch_add_explicit(&g_iTestInteger, 1, memory_order_relaxed);

解決方案四:執行緒區域儲存 + 最後匯總

每個執行緒累加自己的區域變數,結束前用鎖或原子操作加到全域總和,減少鎖競爭。

最佳實踐總結

  1. 避免共享可變狀態:優先用無共享設計(Thread-local、訊息傳遞)。
  2. 必須共享時:使用 pthread_mutex、原子操作、無鎖資料結構。
  3. 正確等待執行緒結束:主執行緒呼叫 pthread_join() 逐一回收,而非 sleep。
  4. 保護區最小化:鎖住的程式碼越少越好,避免在鎖內做 I/O、睡眠。

完整修正版程式碼片段

#include <pthread.h>
#include <stdio.h>

#define THREAD_NUM 100
int g_iTestInteger = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *ProcessTask(void *arg) {
    pthread_mutex_lock(&mutex);
    g_iTestInteger++;
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t threads[THREAD_NUM];
    for (int i = 0; i < THREAD_NUM; i++)
        pthread_create(&threads[i], NULL, ProcessTask, NULL);

    for (int i = 0; i < THREAD_NUM; i++)
        pthread_join(threads[i], NULL);  // 正確等待

    printf("Final value: %d\n", g_iTestInteger);  // 穩定輸出 100
    return 0;
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved