[筆記] Threads in Ruby (2)

上一篇說到 Ruby MRI 的 GIL (Global Interpreter Lock) 特性讓 Ruby 是 single-threaded,這篇來講 Ruby on Rails 中 threads 的狀況。

進一步檢視 GIL 的運作

雖然 GIL 讓 Ruby 的平行作業(parallelism)變得有點困難,它其實並沒有完全阻止。GIl 存在的目的是保護 interpreter 的內在狀態,所以它只適用於 Ruby 的運算。舉例來說,I/O 操作就不會被 GIL 阻擋,像是 Ruby 的 PostgreSQL library 是用 C 語言寫的,所以呼叫資料庫操作的時候就會釋放 GIL。

猜猜下面程式碼會幾秒跑完?

https://gist.github.com/7d6b6fb335a61d8bb67aa71211bded8e.git

答案是一秒!也就表示,資料庫操作的時候 parallelism 是可以發生的。但這同時表示,我們會失去 GIL 對於 context switch 的保護。

(Check-and-Act) Race Condition

一個 GIL 無法防止的經典狀況是 Race Condition。Ruby 的 thread scheduling 演算法可以在任意時間切換不同的 thread,所以無法保證它存取資料的順序。比如說今天同時開了兩個 thread (T1 & T2),都是先確認一個 money_is_sent 的變值是否為真,是的話就結束,否的話就執行 send_money 函式,然後把 money_is_sent 設為真。有可能發生的情況是,程式在執行到 T1 的 send_money 之後,發生了 context switch,換到 T2,這時候 money_is_sent 還沒被更新,所以 send_money 會再被執行一次。多給一次錢,這不是我們想要發生的狀況,是吧?

解法是 Mutex (Mutual Exclusion)

Mutual Exclusion

Mutex 就像是自己設計的 GIL,保護自己的程式碼區塊。只要定義一個Mutex 然後呼叫 synchronize 方法就可以確保一個區塊的完整程式碼被執行完控制權才會被釋放。

https://gist.github.com/jenny-codes/2e3147cd595badab6a16a0067ec822d5

如此一來就可以避免 involuntary context switch 的情況發生。

值得注意的是,想要防止干擾的程式碼要放在同一個 Mutex object 中(用同一個 Mutex object 的 synchronize 方法來管理),如果不同的區段使用不同的 lock,那就沒有用了。

Happy ever after, then?

有了 Mutex 我們就可以放心 multithread 了嗎?當然不。下一篇來介紹 deadlock(還有它的解法)。

雖然已盡能力所及地確保資料的正確性,但我恐怕還是會有不對/不精確的觀念或用字,如果願意指正我的話我會非常感激!

References

Working with Multithreaded Ruby Part I
dev.to

文章同步發表於 Medium


  • Find me at