前言
本文主要給大家介紹了關於ruby並發並行和全局鎖的相關內容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧。
並發和並行
在開發時,我們經常會接觸到兩個概念: 並發和並行,幾乎所有談到並發和並行的文章都會提到一點: 並發並不等於並行.那麽如何理解這句話呢"jb51code">
require 'benchmark' def f1 puts "sleep 3 seconds in f1\n" sleep 3 end def f2 puts "sleep 2 seconds in f2\n" sleep 2 end Benchmark.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 模擬耗時的操作.順序執行時候的消耗時間.
2、並行執行
模擬多線程時的操作
# 接上述代碼 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)
我們發現多線程下耗時和f1的耗時相近,這與我們預期的一樣,采用多線程可以實現並行.
Ruby 的多線程能夠應付 IO Block,當某個線程處於 IO Block 狀態時,其它的線程還可以繼續執行,從而使整體處理時間大幅縮短.
Ruby 中的線程
上述的代碼示例中使用了 ruby 中 Thread 的線程類, Ruby可以很容易地寫Thread類的多線程程序.Ruby線程是一個輕量級的和有效的方式,以實現在你的代碼的並行.
接下來來描述一段並發時的情景
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 "現在需要等3秒才可以看到我:#{Time.now - time}" end test ## 不用等3秒就可以看到我:8.6e-05 ## 現在需要等3秒才可以看到我:3.003699
Thread的創建是非阻塞的,所以文字立即就可以輸出.這樣就模擬了一個並發的行為.每個線程sleep 3 秒,在阻塞的情況下,多線程可以實現並行.
那麽這個時候我們是不是就完成了並行的能力呢"jb51code">
從這裏可以看出,即便我們將同一個任務分成了4個線程並行,但是時間並沒有減少,這是為什麽呢? 因為有全局鎖(GIL)的存在!!! 全局鎖 我們通常使用的ruby采用了一種稱之為GIL的機制. 即便我們希望使用多線程來實現代碼的並行, 由於這個全局鎖的存在, 每次只有一個線程能夠執行代碼,至於哪個線程能夠執行, 這個取決於底層操作系統的實現。 即便我們擁有多個CPU, 也只是為每個線程的執行多提供了幾個選擇而已。 我們上面代碼中每次只有一個線程可以執行 count += 1 . Ruby 多線程並不能重復利用多核 CPU,使用多線程後整體所花時間並不縮短,反而由於線程切換的影響,所花時間可能還略有增加。 但是我們之前sleep的時候, 明明實現了並行啊! 這個就是Ruby設計高級的地方——所有的阻塞操作是可以並行的,包括讀寫文件,網絡請求在內的操作都是可以並行的. 在網絡請求時程序發生了阻塞,而這些阻塞在Ruby的運行下是可以並行的,所以在耗時上大大縮短了. GIL 的思考 那麽,既然有了這個GIL鎖的存在,是否意味著我們的代碼就是線程安全了呢"jb51code">
上述中r 裏 雖然e的前後順序不一樣, 但是@c的值始終保持為 2 ,即每個線程時都能保留好當前的 @c 的值.沒有線程簡的調度. 如果在上述代碼線程中加入 可能會觸發GIL的操作 例如 puts 打印到屏幕: 這個就會觸發GIL的lock, 數據異常了. 小結 Web 應用大多是 IO 密集型的,利用 Ruby 多進程+多線程模型將能大幅提升系統吞吐量.其原因在於:當Ruby 某個線程處於 IO Block 狀態時,其它的線程還可以繼續執行,從而降低 IO Block 對整體的影響.但由於存在 Ruby GIL (Global Interpreter Lock),MRI Ruby 並不能真正利用多線程進行並行計算. PS. 據說 JRuby 去除了GIL,是真正意義的多線程,既能應付 IO Block,也能充分利用多核 CPU 加快整體運算速度,有計劃了解一些. 總結 以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對電腦玩物的支持。
文章來源:
require 'benchmark'
def multiple_threads
count = 0
threads = 4.times.map do
Thread.new do
2500000.times { count += 1}
end
end
threads.map(&:join)
end
def single_threads
time = Time.now
count = 0
Thread.new do
10000000.times { count += 1}
end.join
end
Benchmark.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)
require 'benchmark'
require 'net/http'
# 模擬網絡請求
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)
end
def single_threads
uri = URI("http://www.baidu.com")
Thread.new do
100.times { Net::HTTP.get(uri) }
end.join
end
Benchmark.bm do |b|
b.report { multiple_threads }
b.report { single_threads }
end
user system total real
0.240000 0.110000 0.350000 ( 3.659640)
0.270000 0.120000 0.390000 ( 14.167703)
@a = 1
r = []
10.times do |e|
Thread.new {
@c = 1
@c += @a
r << [e, @c]
}
end
r
## [[3, 2], [1, 2], [2, 2], [0, 2], [5, 2], [6, 2], [7, 2], [8, 2], [9, 2], [4, 2]]
@a = 1
r = []
10.times do |e|
Thread.new {
@c = 1
puts @c
@c += @a
r << [e, @c]
}
end
r
## [[2, 2], [0, 2], [4, 3], [5, 4], [7, 5], [9, 6], [1, 7], [3, 8], [6, 9], [8, 10]]
Tags:
並行 線程 並發 sleep 0.000000 seconds