目錄
-
開發環境準備
-
線程基礎概念
-
進程與線程的關系
-
線程生命周期
-
-
創建線程
-
等待線程結束
-
線程函數和參數
-
互斥鎖與共享資源保護
-
總結
開發環境準備
-
操作系統:以 Linux 為例(Ubuntu/CentOS 等主流發行版)。請確保系統已安裝 GNU C 編譯器(gcc)。
-
線程庫:POSIX 線程庫一般已包含在標準庫中。若未安裝,可以通過包管理器安裝對應開發包。
-
編譯命令:編寫完成代碼后,可用
gcc
編譯,并加上線程選項。示例:
gcc -o myprog main.c -pthread
其中 -pthread
(或 -lpthread
)選項用于鏈接 pthread 庫?blog.csdn.net。例如出現 undefined reference to pthread_create
錯誤時,需要添加此選項。
線程基礎概念
進程與線程的關系
多個線程示意圖展示了同一進程中各線程共享的資源,如代碼段、數據段和打開的文件等?cnblogs.com。線程屬于進程,一個進程可以包含一個或多個線程?cnblogs.com。一般來說,進程是操作系統進行資源分配和調度的最小單位,而線程是程序執行的最小單位?cnblogs.com。同一進程中的多個線程共享進程的內存空間(包括代碼、數據、堆等),但各自擁有獨立的寄存器和棧空間?cnblogs.com。多個線程并發執行時,可以提高程序并行度,但也需要注意同步和互斥。
線程生命周期
如上圖所示,線程在運行過程中會經歷不同狀態。通常一個線程的生命周期包括 新建 (New)、就緒 (Runnable)、運行 (Running)、阻塞/等待 (Blocked/Waiting) 和 終止 (Dead) 等階段?cnblogs.com。當調用 pthread_create()
后,線程從“新建”進入“就緒”狀態;線程獲得 CPU 時間后進入“運行”狀態;如果線程調用 sleep()
、pthread_join()
等阻塞操作,則進入“阻塞”狀態。線程執行完畢或調用 pthread_exit()
后進入“終止”狀態?cnblogs.com。掌握這些狀態轉換有助于理解線程的調度行為和并發執行過程。
創建線程
要創建線程,使用 POSIX 線程庫提供的 pthread_create()
函數。其原型如下:
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
-
thread
:指向pthread_t
類型變量的指針,用于保存新創建線程的 ID。 -
attr
:線程屬性,一般使用默認值NULL
。 -
start_routine
:線程入口函數地址(函數指針)。 -
arg
:傳遞給線程函數的參數,類型為void*
。
示例:創建一個線程執行簡單的打印函數。
#include <stdio.h>
#include <pthread.h>void* say_hello(void* arg) {printf("Hello from thread!\n");return NULL;
}int main() {pthread_t tid;// 創建線程,執行 say_hello 函數pthread_create(&tid, NULL, say_hello, NULL);// 等待線程結束pthread_join(tid, NULL);return 0;
}
以上代碼中,pthread_create(&tid, NULL, say_hello, NULL);
會創建一個新線程,該線程運行 say_hello
函數。主線程在 pthread_create
之后繼續往下執行(此例中主線程隨后調用了 pthread_join
等待子線程結束)。
等待線程結束
創建線程后,主線程和子線程是并發執行的。有時需要主線程等待子線程完成后再繼續,比如輸出結果或回收資源。此時使用 pthread_join()
函數。其原型為:
int pthread_join(pthread_t thread, void **retval);
-
第一個參數為要等待的線程 ID;
-
第二個參數用于獲取線程函數的返回值(可為
NULL
表示不關心返回值)。
上例中 pthread_join(tid, NULL);
會阻塞主線程,直到 tid
對應的子線程執行結束并回收其資源為止。若省略 pthread_join
,主線程可能在子線程完成前就退出,導致子線程被強制終止或無法正常輸出結果。
線程函數和參數
線程入口函數必須符合 void* func(void* arg)
的形式。函數內參數類型為 void*
,可以傳遞任意指針數據。在線程內部,需要根據實際類型將參數指針轉換回來。例如:
#include <stdio.h>
#include <pthread.h>void* print_num(void* arg) {int *p = (int*)arg; // 轉換回 int* 類型printf("num = %d\n", *p);return NULL;
}int main() {pthread_t tid;int value = 42;// 將 &value 作為參數傳入線程pthread_create(&tid, NULL, print_num, &value);pthread_join(tid, NULL);return 0;
}
上例中,主線程定義了一個整數 value = 42
,并將它的地址傳給子線程。子線程在 print_num
函數中把 void*
參數轉換為 int*
后,通過 *p
訪問該值并打印。注意:被傳遞的數據(如 value
)在子線程訪問期間必須有效,如果是局部變量則不能在其作用域結束后再訪問。
互斥鎖與共享資源保護
多個線程同時訪問共享資源(如全局變量或共享數據結構)時,容易發生競態條件。互斥鎖 (mutex) 可以用來保護關鍵代碼區域,確保同一時間只有一個線程訪問共享資源。使用方法如下:
-
定義一個全局互斥鎖變量
pthread_mutex_t lock;
并初始化:pthread_mutex_destroy(&lock);
-
在訪問共享資源前調用
pthread_mutex_lock(&lock);
加鎖,在訪問結束后調用pthread_mutex_unlock(&lock);
解鎖。 -
程序結束時釋放鎖:
pthread_mutex_destroy(&lock);
示例:兩個線程同時對全局變量 counter
進行遞增操作,使用互斥鎖保證結果正確:
#include <stdio.h>
#include <pthread.h>int counter = 0; // 共享資源
pthread_mutex_t lock; // 互斥鎖void* add(void* arg) {for(int i = 0; i < 100000; i++) {pthread_mutex_lock(&lock); // 加鎖counter++;pthread_mutex_unlock(&lock); // 解鎖}return NULL;
}int main() {pthread_t t1, t2;pthread_mutex_init(&lock, NULL); // 初始化互斥鎖pthread_create(&t1, NULL, add, NULL);pthread_create(&t2, NULL, add, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);printf("counter = %d\n", counter);pthread_mutex_destroy(&lock); // 銷毀互斥鎖return 0;
}
在上例中,若不使用鎖,則兩個線程可能會同時讀寫 counter
導致丟失更新。通過加鎖,每次只有一個線程進入臨界區 counter++
,最終輸出的 counter
值才是預期的 200000
。
總結
本文介紹了在 Linux 下使用 C 語言進行多線程編程的入門知識。從環境準備、編譯選項,到線程基本概念(進程與線程的區別、線程生命周期)、以及線程的創建、等待和參數傳遞方法,都做了簡單說明,并給出了最基本的代碼示例。最后還演示了使用 互斥鎖 來保護共享資源的示例。整體思路清晰、示例簡潔,適合 C 語言初學者閱讀。學習多線程編程時,要特別關注線程安全和并發問題,并熟練掌握 POSIX 線程庫的常用函數。