[筆記] Threads in Ruby
Ruby is Single-Threaded
Ruby’s thread is a user-level thread that is originally written. The characteristic of this implementation is a very high portability in both specification and implementation. Surprisingly a MS-DOS can run the thread. Furthermore, you can expect the same response in any environment. Many people mention that this point is the best feature of Ruby.
However, as a trade off for such an extremeness of portability, Ruby abandons the speed. It’s, say, probably the slowest of all user-level thread implementations in this world.
節錄自 Ruby Hacking Guide。
Thread
Thread 是 program 的執行單位。在一個多核心(core)的裝置,作業系統可以將多個 thread 放在不同核心跑,提升整體表現速度。Thread 會儲存自己在 program 中的位置,所以可以很輕易地切換。Ruby 只會跑在一個 thread 中,所以不會有交換的時候(No switching of tasks taking place),也只會跑在一個核心中。但是如果今天是多核心的電腦,如下圖所示,我們要如何才能夠好好利用這個資源呢?
Ruby 有一個 Thread
類別,顧名思義它會幫我們創造一個 native thread 跑在作業系統裡。
不過當我們要使用 Thread
的時候,要記得當 main thread 結束了 program 就會結束,所以如果要確保 program 等到所有線程都跑完才結束,就要加上 ThreadsWait
類別(require ‘thwait’
)並且在程式裡面宣告 ThreadsWait.all_waits(another_thread)
。
所以現在 Ruby 就可以跑在多個 thread 裡面了?不。Ruby 有一個 GIL (Global Interpreter Lock) 特性阻止這件美好事情的發生。
Ruby 的 Global Interpreter Lock
GIL (Global Interpreter Lock) 是一個 Ruby implementation 的特性,它會讓資料一次只能被一個 thread 存取。也就是說,它禁止 parallelism with shared memory(比如說同時存取同一個變數)。所以 another_thread
會在主要程序跑到它願意釋放 GIL(經由 Ruby Runtime)才被執行。誰有 GIL 誰就有 CPU 資源。在這個情況之下,使用 Thread
這個類別只會徒耗資源。
切換 thread 的過程叫做 Context Switching。
為何會有 Global Interpreter Lock anyway? 因為它保障了 thread-safe
的特性。Ruby 的官方 interpreter YARV (Yet Another Ruby VM) 與 MRI (Matz’s Ruby Interpreter)有 GIL,但是不是所有 implementation 都有,比如說 JRuby 與 IronRuby 就沒有。
所以說 Ruby 是一個 single-threaded 語言不是最精確的,應該是說 Ruby 的 implementation 是 single-threaded。
Thread Safety
OS context switch 是一個 involuntary context switch,它會被 kernel’s scheduler 安排,是不可避免也不可預期的。當 context switch 發生在一個式子賦值之前,比如在當 a = a + 1
,正在進行到一半,也就是 a+1,但是還沒有把這個新的值賦予 a 時,context switching 就會讓 a 停留在原本的狀態,造成災難。
GIL 會在 OS context switch 發生時拒絕 interpreter 的存取,強制把 CPU 的控制權還給有 GIL 的 thread。所以 involuntary context switch 發生時只會讓速度慢下來,而不會破壞它。
GIL 的其他好處
- 更快速的 single-threaded programs (MRI 的開發人員曾經嘗試過把 GIL 拿掉,用其他方式保護核心函式庫的 thread-safety,但結果發現這樣 single-threaded 的情況下速度會變很慢,所以放棄了)。
- 使用 C 的函式庫更加容易,因為我們不需要考慮到 thread safety (這跟 MRI 的歷史有關,可以參考這篇文章)。
- 讓 Garbage Collection 更簡單。
雖然已盡能力所及地確保資料的正確性,但我恐怕還是會有不對/不精確的觀念或用字,如果願意指正我的話我會非常感激!
參考資料
stackoverflow.com
文章同步發表於 Medium。