如何在 C 中創建 Linux 線程
在 Linux 上,您可以使用 POSIX 線程 (pthread) 庫在 C/C++ 中創建和管理線程。與其他操作系統不同,Linux 中的線程和進程之間幾乎沒有區別。這就是 Linux 經常將其線程稱為輕量級進程的原因。
使用 pthread 庫,您可以創建線程、等待它們終止以及顯式終止它們。
Linux 上線程使用的歷史
在 Linux 2.6 版本之前,主要的線程實現是 LinuxThreads。這種實現在性能和同步操作方面有很大的限制。可以運行的最大線程數的限制將它們限制在 1000 個以內。
2003 年,由來自 IBM 和 RedHat 的開發人員領導的團隊成功地使Native POSIX Thread Library (NPTL) 項目可用。它首先在 RedHat Enterprise 版本 3 中引入,以解決 Linux 上 Java 虛擬機的性能問題。今天,GNU C 庫包含這兩種線程機制的實現。
這些都不是綠色線程的實現,虛擬機將在純用戶模式下管理和運行。當您使用 pthread 庫時,內核會在每次程序啟動時創建一個線程。
您可以在/proc/<PID>/task下的文件中找到任何正在運行的進程的線程特定信息。這是 procfs Linux 標準下進程信息的標準位置。對於單線程應用,會出現該目錄下有一條與PID值相同的任務記錄。
線程的工作邏輯
線程就像當前在操作系統上運行的進程。在單處理器系統(例如微控制器)中,操作系統內核模擬線程。這允許事務通過切片同時運行。
單核操作系統一次只能真正運行一個進程。然而,在多核或多處理器系統中,這些進程可以同時運行。
C中的線程創建
您可以使用pthread_create函數創建一個新線程。pthread.h頭文件包括其簽名定義以及其他與線程相關的函數。線程使用與主程序相同的地址空間和文件描述符。
pthread 庫還包括對同步操作所需的互斥鎖和條件操作的必要支持。
當您使用 pthread 庫的函數時,您必須確保編譯器將pthread庫鏈接到您的可執行文件中。如有必要,您可以使用-l選項指示編譯器鏈接到庫:
gcc -o test test_thread.c -lpthread
pthread_create 函數具有以下簽名:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
如果程序成功,它返回 0。如果有問題,它會返回一個非零錯誤代碼。在上面的函數簽名中:
- thread參數的類型為pthread_t。創建的線程將始終可以使用此引用訪問。
- attr參數允許您指定自定義行為。您可以使用一系列以pthread_attr_開頭的特定於線程的函數來設置該值。可能的自定義是調度策略、堆棧大小和分離策略。
- start_routine指定線程將運行的函數。
- arg表示由線程傳遞給函數的通用數據結構。
這是一個示例應用程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *worker(void *data)
{
char *name = (char*)data;
for (int i = 0; i < 120; i++)
{
usleep(50000);
printf("Hi from thread name = %s\n", name);
}
printf("Thread %s done!\n", name);
return NULL;
}
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("Exiting from main program\n");
return 0;
}
線程類型
當線程從應用程序中的main()函數返回時,所有線程都會終止,系統會釋放該程序使用的所有資源。同樣,當使用exit()之類的命令退出任何線程時,您的程序將終止所有線程。
使用pthread_join函數,您可以等待線程終止。使用此函數的線程將阻塞,直到預期的線程終止。他們從系統中使用的資源不會返回,即使在可連接線程終止、CPU 未調度或什至無法與ptread_join連接等情況下也是如此。
有時會出現使用 pthread_join 加入沒有意義的情況;例如,如果無法預測線程何時結束。在這種情況下,您可以確保系統在線程返回時自動返回所有資源。
為此,您應該以DETACHED狀態啟動相關線程。啟動線程時,可以通過線程屬性值或使用pthread_detach函數設置DETACH狀態:
int pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int pthread_detach(pthread_t thread);
下面是 pthread_join() 的使用示例。將第一個程序中的 main 函數替換為以下內容:
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("exiting from main program\n");
pthread_join(th1, NULL);
pthread_join(th2, NULL);
return 0;
}
當您編譯並運行該程序時,您的輸出將是:
Hi from thread Y
Hi from thread X
Hi from thread Y
...
Hi from thread Y
exiting from main program
Hi from thread X
...
Hi from thread X
Thread X done!
Hi from thread Y
Thread Y done!
線程終止
您可以通過調用 pthread_cancel 並傳遞相應的pthread_t id 來取消線程:
int pthread_cancel(pthread_t thread);
您可以在以下代碼中看到這一點。同樣,只有主要功能不同:
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(1);
printf("====> Cancelling Thread Y!!\n");
pthread_cancel(th2);
usleep(100000);
printf("====> Cancelling Thread X!\n");
pthread_cancel(th1);
printf("exiting from main program\n");
return 0;
}
為什麼要創建線程?
操作系統總是嘗試在一個或多個 CPU 上運行線程,要么來自自創建的列表,要么來自用戶創建的線程列表。某些線程無法運行,因為它們正在等待來自硬件的輸入/輸出信號。它們也可能是自願等待,等待另一個線程的響應,或者有另一個線程阻塞它們。
您可以調整分配給使用 pthread 創建的線程的資源。這可以是自定義調度策略,或者您可以根據需要選擇調度算法,例如 FIFO 或 Round-robin。
發佈留言