Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 多線程編程:從入門到實踐的完整指南

編輯:Linux 教學

前言

線程技術自 60 年代提出,80 年代中期才真正進入作業系統內核。Linux 採用 POSIX 線程標準,透過 pthread 庫實現。相較於進程,線程共享地址空間,創建/切換開銷極低(約為進程的 1/30),並提供便捷的數據共享機制,是高併發、高響應應用的首選。本文從基本概念出發,逐步深入到屬性設置、同步機制、線程私有數據等進階主題,並提供完整可編譯範例。

一、為什麼需要多線程?

維度進程線程
地址空間獨立共享
創建開銷大(複製頁表、建立數據結構)小(僅分配棧、寄存器上下文)
切換開銷大(涉及 MMU、TLB 刷新)小(用戶/核心態上下文切換)
通信方式IPC(管道、Socket、共享內存)直接讀寫全域/堆變數
穩定性隔離性強,崩潰不影響其他進程一線程崩潰導致整個進程退出

核心優勢:

  1. 提高響應性:GUI 應用將耗時操作放入後台線程,主線程保持界面流暢。
  2. 多核並行:OS 自動將線程調度到不同 CPU 核心,發揮多核性能。
  3. 簡化程式結構:將複雜邏輯拆分為獨立/半獨立線程,降低耦合度。

二、第一個多線程程式

編譯與鏈接

gcc example.c -lpthread -o example

核心 API

  • pthread_create:創建線程。
  • pthread_join:等待線程結束並回收資源。
  • pthread_exit:主動終止當前線程。
  • pthread_t:線程標識符(unsigned long int)。

範例:兩線程並發列印

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

void *thread_func(void *arg) {
    for (int i = 0; i < 3; ++i) {
        printf("子線程 %d\n", i);
        sleep(1);
    }
    return NULL;
}

int main(void) {
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, thread_func, NULL);
    if (ret != 0) {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < 3; ++i) {
        printf("主線程 %d\n", i);
        sleep(1);
    }

    pthread_join(tid, NULL); // 等待子線程結束
    return 0;
}

輸出(交替順序不定,視調度器而定):

主線程 0
子線程 0
主線程 1
子線程 1
主線程 2
子線程 2

三、線程屬性設置

屬性對象 pthread_attr_t 需先 pthread_attr_init 初始化,再用專用函數修改,最後傳給 pthread_create,用完需 pthread_attr_destroy 釋放。

1. 綁定範圍

  • PTHREAD_SCOPE_SYSTEM(綁定):線程 1:1 映射到輕量級進程(LWP),響應快,適合實時任務。
  • PTHREAD_SCOPE_PROCESS(非綁定,預設):多線程多工一個 LWP,由用戶級調度器管理。
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid, &attr, func, NULL);

2. 分離狀態

  • 可連接(預設):必須 pthread_join 回收,否則產生僵屍線程洩漏資源。
  • 分離:線程自行結束自動釋放資源,無法 join。適用於「發射後不管」的任務。
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

陷阱:分離線程若極快結束,可能在 pthread_create 返回前就釋放了線程 ID,導致父線程拿到無效 ID。解決:在子線程開頭 pthread_cond_timedwait 短暫等待,或用互斥鎖同步。

3. 優先級與調度策略

#include <sched.h>
struct sched_param param;
pthread_attr_getschedparam(&attr, &param);
param.sched_priority = 20; // 視調度策略範圍而定
pthread_attr_setschedparam(&attr, &param);
pthread_attr_setschedpolicy(&attr, SCHED_RR); // 或 SCHED_FIFO
pthread_create(&tid, &attr, func, NULL);

需 root 權限或 CAP_SYS_NICE 才能設置實時調度策略。

四、線程同步:保護共享數據

共享數據帶來競態條件,必須用同步原語保護。

1. 互斥鎖

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&mutex);
// 臨界區
pthread_mutex_unlock(&mutex);
  • 預設為「快速互斥鎖」,同一線程重複加鎖會死鎖。
  • 可用 pthread_mutexattr_settype 設置為 PTHREAD_MUTEX_RECURSIVE(遞歸鎖)或 PTHREAD_MUTEX_ERRORCHECK(檢錯鎖)。

2. 讀寫鎖

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

pthread_rwlock_rdlock(&rwlock);  // 讀鎖,共享
pthread_rwlock_wrlock(&rwlock);  // 寫鎖,排他
pthread_rwlock_unlock(&rwlock);

適合「讀多寫少」場景,並發性能優於互斥鎖。

3. 條件變數

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 等待者
pthread_mutex_lock(&mutex);
while (!condition) pthread_cond_wait(&cond, &mutex);
// 處理...
pthread_mutex_unlock(&mutex);

// 通知者
pthread_mutex_lock(&mutex);
condition = true;
pthread_cond_signal(&cond); // 或 broadcast
pthread_mutex_unlock(&mutex);

關鍵:pthread_cond_wait 原子地釋放鎖並休眠,被喚醒後重新獲取鎖,避免競態。

4. 自旋鎖

pthread_spinlock_t spin;
pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE);
pthread_spin_lock(&spin);
// 極短臨界區
pthread_spin_unlock(&spin);

適用於極短臨界區、多核系統,避免線程切換開銷;長時間持有會浪費 CPU。

五、線程私有數據 (Thread-Specific Data, TSD)

解決「全域變數在線程間共享,但每線程需要獨立副本」的問題(如 errno、strtok 狀態、資料庫連接句柄)。

核心 API

  • pthread_key_create(&key, destructor):創建鍵,可選析構函數(線程退出時自動調用)。
  • pthread_setspecific(key, value):綁定值。
  • pthread_getspecific(key):獲取值。
  • pthread_key_delete(key):銷毀鍵(不調用析構函數)。

配合 pthread_once 確保鍵只創建一次

static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t key;

void make_key() { pthread_key_create(&key, free); }

void *get_connection() {
    pthread_once(&once, make_key);
    void *conn = pthread_getspecific(key);
    if (!conn) {
        conn = create_connection();
        pthread_setspecific(key, conn);
    }
    return conn;
}

六、常見陷阱與最佳實踐

陷阱後果對策
函數內 static 變數多線程共享同一份,數據競態改用 TSD 或傳入緩衝區指針
返回指向棧/靜態區的指針其他線程修改導致數據錯亂動態分配內存,由調用者釋放;或用 TSD
共享變數未聲明 volatile編譯器優化導致讀到舊值加 volatile,並配合鎖/原子操作
長時間持有鎖 / 嵌套鎖順序不一死鎖、性能下降縮小臨界區、統一加鎖順序、用 pthread_mutex_trylock
分離線程未同步就極快退出父線程獲取無效 tid子線程開頭等待條件變數或屏障
在信號處理器中調用非 async-signal-safe 函數未定義行為僅調用 write、sig_atomic_t 變數操作等安全函數

七、進階主題速覽

  • 線程池:預創建固定數量線程,任務隊列分發,避免頻繁創建/銷毀開銷。
  • 無鎖編程:利用原子操作(__sync_*、C11 atomic_*)實現無鎖隊列、引用計數等。
  • NUMA 感知調度:numactl、pthread_setaffinity_np 綁定線程到特定 CPU 節點,優化記憶體訪問局部性。
  • C++11 std::thread:RAII 風格封裝,配合 std::mutex、std::condition_variable、std::future 更安全易用。

結語

Linux 多線程編程的核心在於「共享與同步的平衡」。熟練掌握 pthread API、屬性配置、同步原語選型、TSD 機制,並警惕常見陷阱,即可構建高性能、高可靠的併發應用。建議在實戰中優先考慮 C++11/17 標準庫或成熟線程池庫,減少底層錯誤機會。

Copyright © Windows教程網 All Rights Reserved