2017年8月26日 星期六

Thread Safety

thread-safe 函數給多個 thread 同時重複呼叫時不會造成錯誤,包括原本就已經是 reentrant 的函數,以及用 mutex 或其它方式改良過的函式。

thread 排程不是 preemptive 會嗎?

threadFunc() 為例,對全域變數 glob 作 loops 次「加一」。當有兩個 thread t1 和 t2 都呼叫此函數,「加一」動作並不是 atomic operation,實際上包括了「讀 glob」、「glob 加 1」、和「寫 glob」,有時中間會插入另一個 thread,而造成結果被覆蓋而錯誤。
t1 讀 glob 為 1
t2 讀 glob 為 1
t2 加 1 後寫 glob 為 2
t1 加 1 後寫 glob 為 2
結果是 2,但正確結果是 3。

註:glob 是 int,所以 read 跟 write 可以一次完成。如果資料量大於 int 或沒對齊 int boundary,read 跟 write 無法一次完成,也可能插入其它 thread 的動作。

改正方式:
  1. 使用 mutex (如有 mutex 的 threadFunc())。例如一個變數配置一個 mutex,用在存取需要 critical sections 的部份。
  2. 一次只讓一個 thread 執行 (serialized)。如果 thread 花很多時間執行此函數,會失去 concurrency。
POSIX 裡的函數,有列出除了哪些以外,都必須是 thread-safe (可見 man pthreads)

reentrant 函數能夠不用 mutex 達到 thread safety。reentrant 函數
  • 不使用全域變數 (要回給 caller 或呼叫間需要保留的任何資訊,必須存在 caller 配置的 buffers。)
  • 不改變自身程式碼
  • 不呼叫 no-reentrant 函數

有些標準函數不是 reentrant 的,但有另外提供名稱後置 _r 的 reentrant 版本,這些函數需要 buffer 回傳結果。

例如 asctime_r()、ctime_r()、getgrgid_r()、getgrnam_r()、getlogin_r()、getpwnam_r()、getpwuid_r()、gmtime_r()、localtime_r()、rand_r()、readdir_r()、strerror_r()、strtok_r()、及 ttyname_r()。

glibc 提供的 crypt_r()、gethostbyname_r()、getservbyname_r()、getutent_r()、getutid_r()、getutline_r()、及 ptsname_r()。

參考:
  1. :《The Linux Programming Interface》chap. 31.1
  2. 可重入與執行緒安全 (reentrant vs thread-safe) Part 1/Part 2/Part 3
  3. coroutine
  4. https://en.wikipedia.org/wiki/Non-blocking_algorithm
  5. https://en.wikipedia.org/wiki/Real-time
  6. Emulated atomic operations and real-time scheduling,處理器原生只支援最基本的 atomic operations,如何 emulate 其它 atomic operation?在Linux kernel 只需要暫時關閉中斷 (SMP 不行)。

必須避免執行緒同時修改資料結構,破壞了資料的正確性

如果要鎖住資料結構,通常需要 read-modify-write

  1. 檢查目前是否有人正在修改 (有 read 動作)
  2. 如果沒人在修改,就先「鎖上」後修改 (modify-write)
硬體必須提供特別的指令,可以同時 1. 檢查 2. 檢查通過立刻上鎖

POSIX 提供高階的鎖定機制,如:semaphore、mutex、spinlock、rwlock

沒有留言:

張貼留言

SIP header Via

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