作業系統中的POSIX執行緒


POSIX執行緒標準由POSIX執行緒遵循,有時也稱為pthreads。透過使用執行緒,可以將單個任務分解成多個可以同時執行的獨立任務,從而使程式並行化。作業系統中的執行緒可以是使用者級執行緒或核心級執行緒,並由核心進行管理。核心級執行緒由作業系統管理,而使用者級執行緒完全由應用程式控制。核心級執行緒包括POSIX執行緒。

POSIX執行緒標準定義了一個執行緒建立和操作的API。此API中的函式允許您啟動新執行緒、修改執行緒屬性、同步執行緒和終止執行緒。POSIX執行緒在許多類Unix作業系統(包括Linux、macOS和FreeBSD)以及某些其他作業系統中使用。由於其標準化的API和高效的實現,它們是建立多執行緒程式的流行選擇。

執行緒的概念

執行緒通常是一個輕量級過程,可以在同一程序內與其他執行緒並行執行。所有執行緒共享全域性變數、靜態變數、堆記憶體和棧。雖然它們可以共享堆記憶體和棧,但執行緒有自己的棧空間。執行緒可以透過訊息傳遞和共享記憶體來相互通訊。

C語言中POSIX執行緒的示例程式碼

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
 
#define NUM_THREADS 5
 
void *PrintHello(void *threadid) {
	long tid;
	tid = (long)threadid;
	printf("Hello World! It's me, thread #%ld!\n", tid);
	pthread_exit(NULL);
}
 
int main(int argc, char *argv[]) {
	pthread_t threads[NUM_THREADS];
	int rc;
	long t;
 
	for (t = 0; t < NUM_THREADS; t++) {
    	printf("In main: creating thread %ld
", t); rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t); if (rc) { printf("ERROR; return code from pthread_create() is %d
", rc); exit(-1); } } pthread_exit(NULL); }

輸出

In main: creating thread 0
In main: creating thread 1
Hello World! It's me, thread #0!
In main: creating thread 2
In main: creating thread 3
In main: creating thread 4
Hello World! It's me, thread #4!
Hello World! It's me, thread #2!
Hello World! It's me, thread #1!
Hello World! It's me, thread #3!

在這個例子中,建立了NUM_THREADS個執行緒,每個執行緒列印一條訊息。執行緒使用PrintHello函式啟動。PrintHello函式用作啟動例程,pthread_create用於在主函式中建立執行緒。然後,我們使用pthread_exit等待每個執行緒完成其任務。

在PrintHello函式中,我們將threadid輸入轉換為長整型資料型別,並用它來列印一條訊息,標識正在釋出訊息的執行緒。最後,我們呼叫pthread_exit來終止執行緒。

pthread_create函式需要四個引數:執行緒啟動例程(在本例中為PrintHello)、執行緒ID、一個指向pthread_t變數的指標,該變數將儲存新執行緒的ID、一個指向pthread_attr_t變數的指標,該變數指定執行緒屬性(我們傳遞NULL以使用預設屬性),以及傳遞給啟動例程的void指標。

如果pthread_create返回一個非零數字,則表示在建立執行緒時發生錯誤。在這種情況下,我們輸出一條錯誤訊息並執行非零狀態碼程式退出。

需要注意的是,執行緒可能不會按照特定的順序執行。每次執行此程式時,輸出訊息的順序可能看起來不同。

為了構建和連結它,您必須使用-pthread選項將程式與pthread庫連結:

$ gcc -pthread example.c -o example

此程式將生成NUM_THREADS個執行緒,每個執行緒都將列印一條包含執行緒標識的訊息。當所有執行緒都執行完畢後,程式結束。

執行緒管理

有多個函式可用於管理執行緒,包括:

  • 使用pthread_join()等待執行緒完成。該函式的兩個輸入是將要加入的執行緒的執行緒ID和一個將用於儲存執行緒退出狀態的變數的引用。

  • 第二種方法pthread_detach()允許將執行緒與父執行緒分離。當分離的執行緒終止時,資源會立即釋放。

  • 使用pthread_cancel()函式終止執行緒。當執行緒被取消時,它的執行立即終止。

執行緒同步

在多執行緒程式設計中,執行緒同步對於防止競爭條件和其他與併發相關的錯誤至關重要。Pthreads提供多種同步機制,包括:

互斥鎖

互斥鎖用於防止對共享資源的併發訪問。互斥鎖提供互斥,限制了同時可以持有互斥鎖的執行緒數。分別使用pthread_mutex_lock()和pthread_mutex_unlock()函式來獲取和釋放互斥鎖。

條件變數

這些變數充當事件的標誌和訊號。條件變數與互斥鎖相關聯,並且必須在等待條件變數之前鎖定互斥鎖。分別使用pthread_cond_wait()和pthread_cond_signal()函式來等待和發出條件變數的訊號。

訊號量

使用訊號量來控制對共享資源的訪問。訊號量跟蹤一個計數器,執行緒可以增加或減少該計數器。當計數器達到0時,等待訊號量的執行緒會被阻塞,直到計數器被增加。使用pthread_mutex_init()、pthread_mutex_destroy()、pthread_cond_init()、pthread_cond_destroy()、sem_init()、sem_destroy()、wait()和post()函式來初始化、銷燬、等待和發出同步原語的訊號。

執行緒安全和死鎖

多執行緒程式設計需要執行緒安全,這確保執行緒正確地訪問共享資源。多個執行緒可以同時呼叫執行緒安全的函式,而不會導致競爭條件或其他與併發相關的錯誤。Pthreads提供多種方法來確保執行緒安全,包括:

互斥鎖

互斥鎖用於防止對共享資源的併發訪問。執行緒可以在使用共享資源之前使用互斥鎖鎖定它,並在之後解鎖它。這確保一次只有一個執行緒可以訪問共享資源。

原子操作

原子操作是在單個、不可中斷的步驟中執行的操作,不會受到其他執行緒的干擾。Pthreads提供許多原子操作,包括atomic_add()、atomic_sub()、atomic_and()、atomic_or()和atomic_xor()。

執行緒區域性儲存

可以使用此技術建立資料,但只能由建立它的執行緒訪問。這對於儲存執行緒特定資料(如執行緒ID)可能很有用,而無需使用全域性或靜態變數。

多執行緒程式設計中可能發生的另一個問題是死鎖。當兩個或多個執行緒都在等待另一個執行緒釋放資源時,就會發生死鎖。Pthreads提供多種方法來避免死鎖,包括:

鎖順序

使用鎖順序,您可以確保按照特定順序獲取鎖。透過以相同的順序獲取鎖,執行緒可以避免可能導致死鎖的迴圈依賴關係。

超時

超時是一種在一段時間後解鎖鎖的方法。透過使用超時,執行緒可以避免無限期地等待鎖釋放,這可能導致死鎖。

死鎖檢測

此技術用於檢測和解決死鎖。及早檢測死鎖使執行緒能夠採取適當的行動,例如釋放鎖,從而打破死鎖。

除錯和故障排除

由於執行緒的非確定性性質,除錯和故障排除多執行緒應用程式可能具有挑戰性。Pthreads提供多種除錯和故障排除工具,包括:

執行緒安全除錯

這種除錯多執行緒應用程式的方法可以防止競爭條件和其他與併發相關的錯誤。Pthreads提供許多執行緒安全除錯工具,包括gdb和valgrind。

除錯工具

可以使用strace和ltrace等除錯工具來跟蹤執行緒執行的系統呼叫和庫呼叫。透過跟蹤呼叫,開發人員可以查詢諸如競爭條件、死鎖和效能瓶頸等問題。

日誌記錄和跟蹤

這些技術用於記錄執行緒執行期間的行為資訊。透過使用日誌記錄和跟蹤,開發人員可以識別諸如競爭條件、死鎖和效能瓶頸等問題,並檢查執行緒在執行時的行為。

結論

Pthreads是一個強大的多執行緒程式設計工具,提供了多種管理執行緒、同步執行緒和確保執行緒安全的技術。對於開發複雜的、多執行緒的應用程式,Pthreads提供了許多先進的功能,包括執行緒終止、執行緒區域性儲存、條件變數和讀寫鎖。儘管構建多執行緒程式可能具有挑戰性,但Pthreads可以幫助程式設計師開發可靠且高效的多執行緒應用程式。透過理解多執行緒程式設計的原理和實踐,開發人員可以充分利用現代多核處理器的能力,設計出具有卓越效能和可擴充套件性的程式。

更新於:2023年7月19日

941 次瀏覽

開啟您的職業生涯

透過完成課程獲得認證

開始學習
廣告
© . All rights reserved.