2014年4月30日 星期三

pthread

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 結束並不會釋出資源,除非設成 detached 或者其它 thread 用 pthread_join() 等候其結束。設成 detached 可執行 pthread_detach() 或建立 thread 時用 pthread_attr_setdetachstate() 設定屬性。等候結束除了 pthread_join() 外,還可以用 pthread_tryjoin_np() 跟 pthread_timedjoin_np()。

內部

  • thread 建立採用 Linux clone() 系統呼叫,相對於 process 建立使用 fork() 系統呼叫大概快十倍,不用複製 page tables、許多屬性共用、資料也不用 copy-on-write。
  • thread synchronization primitives (mutexes、thread joining 等等) 使用Linux futex() 系統呼叫實作。
  • 用了前兩個 real-time signals

參考

  1. man-page pthreads
  2. man pthread_create
  3. :《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);

沒有留言:

張貼留言

SIP header Via

所有 SIP 訊息 都要有 Via,縮寫 v。一開始的 UAC 和後續途經的每個 proxy 都會疊加一個 Via 放傳送的位址,依序作為回應的路徑。 格式 sent-protocol sent-by [ ;branch= branch ][ ; 參數 ...] s...