最近,在學(xué)習(xí)Ruby時(shí),武林技術(shù)頻道小編總想和大家分享詳解ruby中并發(fā)并行與全局鎖,本文主要介紹詳解ruby中并發(fā)并行與全局鎖,希望對(duì)你學(xué)習(xí)有幫助!
并發(fā)和并行
在開發(fā)時(shí),我們經(jīng)常會(huì)接觸到兩個(gè)概念: 并發(fā)和并行,幾乎所有談到并發(fā)和并行的文章都會(huì)提到一點(diǎn): 并發(fā)并不等于并行.那么如何理解這句話呢?
將這個(gè)例子擴(kuò)展到我們的web開發(fā)中, 就可以這樣理解:
根據(jù)上述所描述的例子,我們?cè)?ruby 中怎么去模擬出這樣的一個(gè)并發(fā)行為呢? 看下面這一段代碼:
1、順序執(zhí)行:
模擬只有一個(gè)線程時(shí)的操作.
require 'benchmark'def f1 puts "sleep 3 seconds in f1/n" sleep 3enddef f2 puts "sleep 2 seconds in f2/n" sleep 2 endBenchmark.bm do |b| b.report do f1 f2 end end## ## user system total real## sleep 3 seconds in f1## sleep 2 seconds in f2## 0.000000 0.000000 0.000000 ( 5.009620)
上述代碼很簡單,用 sleep 模擬耗時(shí)的操作.順序執(zhí)行時(shí)候的消耗時(shí)間.
2、并行執(zhí)行
模擬多線程時(shí)的操作
# 接上述代碼Benchmark.bm do |b| b.report do threads = [] threads << Thread.new { f1 } threads << Thread.new { f2 } threads.each(&:join) end end#### user system total real## sleep 3 seconds in f1## sleep 2 seconds in f2## 0.000000 0.000000 0.000000 ( 3.005115)我們發(fā)現(xiàn)多線程下耗時(shí)和f1的耗時(shí)相近,這與我們預(yù)期的一樣,采用多線程可以實(shí)現(xiàn)并行.
Ruby 的多線程能夠應(yīng)付 IO Block,當(dāng)某個(gè)線程處于 IO Block 狀態(tài)時(shí),其它的線程還可以繼續(xù)執(zhí)行,從而使整體處理時(shí)間大幅縮短.
Ruby 中的線程
上述的代碼示例中使用了 ruby 中 Thread 的線程類, Ruby可以很容易地寫Thread類的多線程程序.Ruby線程是一個(gè)輕量級(jí)的和有效的方式,以實(shí)現(xiàn)在你的代碼的并行.
接下來來描述一段并發(fā)時(shí)的情景
def thread_test time = Time.now threads = 3.times.map do Thread.new do sleep 3 end end puts "不用等3秒就可以看到我:#{Time.now - time}" threads.map(&:join) puts "現(xiàn)在需要等3秒才可以看到我:#{Time.now - time}" end test ## 不用等3秒就可以看到我:8.6e-05 ## 現(xiàn)在需要等3秒才可以看到我:3.003699Thread的創(chuàng)建是非阻塞的,所以文字立即就可以輸出.這樣就模擬了一個(gè)并發(fā)的行為.每個(gè)線程sleep 3 秒,在阻塞的情況下,多線程可以實(shí)現(xiàn)并行.
那么這個(gè)時(shí)候我們是不是就完成了并行的能力呢?
很遺憾,我上述的描述中只是提到了我們?cè)诜亲枞那闆r下可以模擬了并行.讓我們?cè)倏匆幌聞e的例子:
require 'benchmark'def multiple_threads count = 0 threads = 4.times.map do Thread.new do 2500000.times { count += 1} end end threads.map(&:join)enddef single_threads time = Time.now count = 0 Thread.new do 10000000.times { count += 1} end.joinendBenchmark.bm do |b| b.report { multiple_threads } b.report { single_threads }end## user system total real## 0.600000 0.010000 0.610000 ( 0.607230)## 0.610000 0.000000 0.610000 ( 0.623237)從這里可以看出,即便我們將同一個(gè)任務(wù)分成了4個(gè)線程并行,但是時(shí)間并沒有減少,這是為什么呢?
因?yàn)橛腥宙i(GIL)的存在?。?!
全局鎖
我們通常使用的ruby采用了一種稱之為GIL的機(jī)制.
即便我們希望使用多線程來實(shí)現(xiàn)代碼的并行, 由于這個(gè)全局鎖的存在, 每次只有一個(gè)線程能夠執(zhí)行代碼,至于哪個(gè)線程能夠執(zhí)行, 這個(gè)取決于底層操作系統(tǒng)的實(shí)現(xiàn)。
即便我們擁有多個(gè)CPU, 也只是為每個(gè)線程的執(zhí)行多提供了幾個(gè)選擇而已。
我們上面代碼中每次只有一個(gè)線程可以執(zhí)行 count += 1 .
Ruby 多線程并不能重復(fù)利用多核 CPU,使用多線程后整體所花時(shí)間并不縮短,反而由于線程切換的影響,所花時(shí)間可能還略有增加。
但是我們之前sleep的時(shí)候, 明明實(shí)現(xiàn)了并行?。?/p>
這個(gè)就是Ruby設(shè)計(jì)高級(jí)的地方——所有的阻塞操作是可以并行的,包括讀寫文件,網(wǎng)絡(luò)請(qǐng)求在內(nèi)的操作都是可以并行的.
require 'benchmark'require 'net/http'# 模擬網(wǎng)絡(luò)請(qǐng)求def multiple_threads uri = URI("http://www.baidu.com") threads = 4.times.map do Thread.new do 25.times { Net::HTTP.get(uri) } end end threads.map(&:join)enddef single_threads uri = URI("http://www.baidu.com") Thread.new do 100.times { Net::HTTP.get(uri) } end.joinendBenchmark.bm do |b| b.report { multiple_threads } b.report { single_threads }end user system total real0.240000 0.110000 0.350000 ( 3.659640)0.270000 0.120000 0.390000 ( 14.167703)在網(wǎng)絡(luò)請(qǐng)求時(shí)程序發(fā)生了阻塞,而這些阻塞在Ruby的運(yùn)行下是可以并行的,所以在耗時(shí)上大大縮短了.
GIL 的思考
那么,既然有了這個(gè)GIL鎖的存在,是否意味著我們的代碼就是線程安全了呢?
很遺憾不是的,GIL 在ruby 執(zhí)行中會(huì)某一些工作點(diǎn)時(shí)切換到另一個(gè)工作線程去,如果共享了一些類變量時(shí)就有可能踩坑.
那么, GIL 在 ruby代碼的執(zhí)行中什么時(shí)候會(huì)切換到另外一個(gè)線程去工作呢?
有幾個(gè)明確的工作點(diǎn):
一個(gè)例子
@a = 1r = []10.times do |e|Thread.new { @c = 1 @c += @a r << [e, @c]}endr## [[3, 2], [1, 2], [2, 2], [0, 2], [5, 2], [6, 2], [7, 2], [8, 2], [9, 2], [4, 2]]上述中r 里 雖然e的前后順序不一樣, 但是@c的值始終保持為 2 ,即每個(gè)線程時(shí)都能保留好當(dāng)前的 @c 的值.沒有線程簡的調(diào)度.
如果在上述代碼線程中加入 可能會(huì)觸發(fā)GIL的操作 例如 puts 打印到屏幕:
@a = 1r = []10.times do |e|Thread.new { @c = 1 puts @c @c += @a r << [e, @c]}endr## [[2, 2], [0, 2], [4, 3], [5, 4], [7, 5], [9, 6], [1, 7], [3, 8], [6, 9], [8, 10]]這個(gè)就會(huì)觸發(fā)GIL的lock, 數(shù)據(jù)異常了.
小結(jié)
Web 應(yīng)用大多是 IO 密集型的,利用 Ruby 多進(jìn)程+多線程模型將能大幅提升系統(tǒng)吞吐量.其原因在于:當(dāng)Ruby 某個(gè)線程處于 IO Block 狀態(tài)時(shí),其它的線程還可以繼續(xù)執(zhí)行,從而降低 IO Block 對(duì)整體的影響.但由于存在 Ruby GIL (Global Interpreter Lock),MRI Ruby 并不能真正利用多線程進(jìn)行并行計(jì)算.
PS. 據(jù)說 JRuby 去除了GIL,是真正意義的多線程,既能應(yīng)付 IO Block,也能充分利用多核 CPU 加快整體運(yùn)算速度,有計(jì)劃了解一些.
總結(jié)
以上是本文的介紹的詳解ruby中并發(fā)并行與全局鎖全部內(nèi)容。希望本文的內(nèi)容對(duì)每個(gè)人的學(xué)習(xí)或工作的人都有一定的借鑒意義。如果您有任何問題,可以留言和交流。
新聞熱點(diǎn)
疑難解答
圖片精選