pthread、POSIX thread、執行緒。
程式 (program) 執行成為 process,一開始本身就是一個 thread,可建立其它 thread 在同一個虛擬記憶體下同時進行,共用 text、data 及 heap 區段,但有獨立的堆疊 (stack) 而有各自的函數呼叫及自動變數儲存。thread 之間的溝通可直接用放在 data 區段的全域變數,不需要使用負擔較重的 IPC。thread 的建立、結束、執行切換也比 process 快。
目前在 Linux 的主要實作是 Native POSIX Threads Library (NPTL),使用 cc -pthread 編譯。
屬性 (attributes) 有分是 process 層級和 thread 層級。process 層級屬性是 thread 共用:
- process ID、parent process ID、PGID、SID、和控制終端機、user 和 group ID。
- 開啟的 file descriptors、file mode creation mask (見 umask())
- record locks (見 fcntl())
- signal dispositions
- current directory (見 chdir()) 及 root directory (見 chroot())
- interval timers (見 setitimers()) 及 POSIX timers (見 timer_create())
- nice 值:在 POSIX 是 per-process 設定,但目前 Linux/NPTL 實作是 per-thread 屬性。
- resource limits (setrlimit())
- 測量消耗的 CPU 時間 (times()) 及資源 (見 getrusage())
thread 層級是各自 thread 獨立:
- thread ID:pthread_t,在 pthread_create() 時建立,在 process 內有唯一性。thread 結束後釋出可能再利用。thread_self() 可以取得自己的 thread ID。
- Task ID:kernel 對 thread 另外編排的 ID,透過 gettid() 取得,也就是在 user space 工具程式 (例如 ps、top) 看到的 PID (Process ID)。
- TGID (Thread Group ID):process 中第一個 thread 的 task ID,透過 getpid() 取得。
- PPID:是親 process 的 task ID。
- PGID、SID
- signal mask (pthread_sigmask()):繼承
- 繼承 mask (pthread_sigmask()),清空 pending (sigpending())
- 不繼承 alternate signal stack (sigaltstack())
- errno 變數 (不是全域變數,需 #include <errno.h> 使用)
- 浮點計算環境 (fenv()):繼承
- real-time scheduling policy and priority (sched_setscheduler() 及 sched_setparam())
- capabilities
- CPU affinity (sched_setaffinity):親和力,多處理器系統的個別處理器如何利用。
- stack
- CPU-time:pthread_getcpuclockid() 取得 thread 的 clock ID (process 固定為CLOCK_PROCESS_CPUTIME_ID,calling thread 用 CLOCK_THREAD_CPUTIME_ID。),在透過 clock_gettime() 取得時間
pthreads API 函數的回傳值:大部分成功時回傳 0、失敗時回傳錯誤碼,並不更動 errno。
thread 雖然比 process 效率好,但有些缺點:
- 非 thread-safe 函數多個 thread 同時呼叫可能造成錯誤。
- thread 間共用虛擬記憶體,隔絕性較差,問題較容易互相干擾。
- 每個 thread 佔用 process 部份虛擬記憶體,當 thread 很多的時候可能一個虛擬記憶體不夠用。
- signal 處理
- process 可以跑不同程式,而 thread 需要整合進來
- 共用屬性視情況可能有好有壞
建立
#include <pthread.h> int pthread_create(pthread_t *threadid, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
使用 attr 的設定建立一個執行 start_routine(arg) 的新 thread,而且在 threadid 回傳 pthread_t。
attr 可設定 stack、guard、排程等,NULL 時則使用預設值。ulimit -s 可設預設 stack 大小。
guard 是 stack 之後額外的 page 空間 ,是不可存取的虛擬記憶體空間,讓 stack overflow 會有 SIGSEGV 的效果。在 NPTL 實作是算在 stack 一部分,但 POSIX.1 或廢棄的 LinuxThreads 實作則是外加。
問題:新 thread 何時開始執行?
結束
方式有:
- start_routine() 用 return 結束,回傳值傳給 pthread_join()。
- thread 自己呼叫 pthread_exit(void *retval),*retval 為傳給 pthread_join() 的結束值。*retval 應避免指到 thread 的堆疊。會進行 thread cleanup 動作。
- 其它 thread 呼叫 pthread_cancel()。會進行 thread cleanup 動作。
- 任一 thread 呼叫 exit()、或 process 的 main() 主程式結束,所有 thread 都會結束
內部
- thread 建立採用 Linux clone() 系統呼叫,相對於 process 建立使用 fork() 系統呼叫大概快十倍,不用複製 page tables、許多屬性共用、資料也不用 copy-on-write。
- thread synchronization primitives (mutexes、thread joining 等等) 使用Linux futex() 系統呼叫實作。
- 用了前兩個 real-time signals
參考
- man-page pthreads
- man pthread_create
- 書:《The Linux Programming Interface》chap. 29
- pthread_yield():非標準,改用 POSIX sched_yield() 及 #include <sched.h>
- 看 thread 狀態:ps -T [-p <pid>]、top -H [-p <pid>]、htop
- concurrency and parallel
- cond, sema
- #include <syscall.h>
int id = syscall(SYS_gettid);