
Ruby 多線程
每個正在系統上運作的程式都是一個程序。每個程序又包含一到多個線程。
線程是程式中一個單一的順序控制流程,在單個程式中可以同時運作多個線程完成不同的工作,稱為多線程。
Ruby 中我們可以通過 Thread 類來建立多線程,
Ruby的線程是一個輕量級的,可以以高效的方式來實作并行的代碼。
建立 Ruby 線程
要啟動一個新的線程,隻需要調用 Thread.new 即可:
# 線程 #1 代碼部分
Thread.new {
# 線程 #2 執行代碼
}
# 線程 #1 執行代碼
執行個體
以下執行個體展示了如何在Ruby程式中使用多線程:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
def showGirlAge
loliAge = 8
while loliAge < 14
puts "1--->#{Time.now} loli age is: #{loliAge}"
sleep(2)
loliAge = loliAge + 1
end
end
def showGirlCup
cupArr = Array['A','B','C','D','E','F'];
j = 0
while j < cupArr.length
puts "2--->#{Time.now} girl cup is: #{cupArr[j]}"
sleep(1)
j = j + 1
end
end
puts "主線程開始:#{Time.now}"
thread_1 = Thread.new{
#必須是一個block代碼塊,注意:Ruby的子線程建立出來後,會自動執行,無需手動啟動!!!
showGirlAge()
}
thread_2 = Thread.new{
#必須是一個block代碼塊,注意:Ruby的子線程建立出來後,會自動執行,無需手動啟動!!!
showGirlCup()
}
# 這個方法會阻塞主線程,直到join進來的子線程執行完畢,才會往下繼續執行主線程
thread_1.join
thread_2.join
puts "主線程結束:#{Time.now}"
以上代碼執行結果為:
線程生命周期
1、線程的建立可以使用Thread.new,同樣可以以同樣的文法使用Thread.start 或者Thread.fork這三個方法來建立線程。
2、建立線程後無需啟動,線程會自動執行。
3、Thread 類定義了一些方法來操控線程。線程執行Thread.new中的代碼塊。
4、線程代碼塊中最後一個語句 是線程的值,可以通過線程的方法來調用,如果線程執行完畢,則傳回 線程的值,否則不傳回值直到線程執行完畢。
5、Thread.current 方法傳回表示目前線程的對象。 Thread.main 方法傳回主線程。
6、通過 Thread.Join 方法來執行線程,這個方法會挂起主線程,直到目前線程執行完畢。
線程狀态
線程有5種狀态:
線程狀态 | 傳回值 |
---|---|
Runnable | run |
Sleeping | Sleeping |
Aborting | aborting |
Terminated normally | false |
Terminated with exception | nil |
線程和異常
當某線程發生異常,且沒有被rescue捕捉到時,該線程通常會被無警告地終止。
但是,若有 其它線程 因為Thread#join的關系一直在等待該線程的話,則那個等待中的線程同樣會被引發相同的異常。
示範代碼如下:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
begin
thread = Thread.new do
#pass:将運作權交給其他線程. 它不會改變運作中的線程的狀态,而是将控制權交給其他可運作的線程(顯式的線程排程)。
puts "#{Thread.current}---子線程 before pass"
Thread.pass
puts "#{Thread.current}---子線程 after pass"
#
raise "A Big Bad Bug Found"
end
puts "#{Thread.current}:主線程, before thread.join"
#一旦子線程join了,主線程必須原地等待
#但是,這兒由于在子線程裡,raise了一個exception,是以下面的after thread.join就不會輸出了,而是跳轉到rescue中去了
thread.join
#下面這一句無法輸出,原因見上面
puts "#{Thread.current}:主線程, after thread.join,即子線程已經成功運作完畢"
rescue
puts "#{Thread.current}:rescue start-->"
#<RuntimeError: A Bad Apple>
p $!
puts "#{Thread.current}:rescue end<--"
end
運作效果如下:
使用下列3個方法,就可以讓解釋器在某個線程因異常而終止時中斷運作。
- 啟動腳本時指定-d選項,并以調試模式運作。
- 用
設定标志。Thread.abort_on_exception
- 使用
對指定的線程設定标志。Thread#abort_on_exception
當使用上述3種方法之一後,整個解釋器就會被中斷。
t = Thread.new { ... }
t.abort_on_exception = true
線程同步控制
在Ruby中,提供三種實作同步的方式,分别是:
1. 通過Mutex類實作線程同步
2. 監管資料交接的Queue類實作線程同步
3. 使用ConditionVariable實作同步控制
通過Mutex類實作線程同步
通過Mutex類實作線程同步控制,
如果在多個線程鐘同時需要一個程式變量,可以将這個變量部分使用lock鎖定。
代碼如下:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'thread'
@girlTotalNumber = 10
#互斥鎖
@mutex = Mutex.new
def loveGirl(numberOfLover)
#對共享通路時,加互斥鎖
@mutex.lock
if @girlTotalNumber >= numberOfLover
@girlTotalNumber = @girlTotalNumber - numberOfLover
puts "#{Thread.current},after #{numberOfLover} girl you loved, then girl remains: #{@girlTotalNumber}"
else
puts "#{Thread.current},you wanna #{numberOfLover} girl to love,but girl remains: #{@girlTotalNumber}"
end
#釋放互斥鎖
@mutex.unlock
end
puts "主線程開始--->"
#boya1想要fall in love 6次,每次love 2個girl
boya_1 = Thread.new 6 do
6.times do |value|
numberOfLover = 2
loveGirl(numberOfLover)
#love比較累,休息時間要久一點
sleep 0.02
end
end
#boya2想要fall in love 3次,每次love 1個girl
boya_2 = Thread.new 3 do
3.times do |variable|
numberOfLover = 1
loveGirl(numberOfLover)
sleep(0.01)
end
end
puts "主線程:before sleep --->"
sleep 1
puts "主線程:after sleep <---"
puts "主線程:before 子線程1 join--->"
boya_1.join
puts "主線程:after 子線程1 join<---"
puts "主線程:before 子線程2 join--->"
boya_2.join
puts "主線程:after 子線程1 join<---"
puts "主線程結束<---"
輸出結果如下:
補充一下:
除了使用lock鎖定變量,
還可以使用try_lock鎖定變量,
還可以使用Mutex.synchronize同步對某一個變量的通路。
監管資料交接的Queue類實作線程同步
Queue類就是表示一個支援線程的隊列,能夠 同步地 對隊列末尾進行通路。
不同的線程可以使用統一個隊列,但是不用擔心這個隊列中的資料是否能夠同步,
另外使用SizedQueue類,還能夠限制隊列的長度喔
SizedQueue類能夠非常便捷的幫助我們開發線程同步的應用程式,因為隻要加入到這個隊列中了,就不用去關心線程的同步問題。
經典的生産者消費者問題:
疑問:
#為啥能實作???Excuse Me???
#消費者在隊列Queue中pop時,如果沒有東西的話,就會一直阻塞等待着,直到queue中有東西出來後才繼續向下執行Why???
# 當消費者能夠從隊列中,取出東西時候,就列印
# 如果隊列中pop不出東西的話,就會一直等待着???Why???
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'thread'
queue = Queue.new
#生産者
producer = Thread.new do
10.times do |i|
#故意生産得很慢很慢
sleep rand(i)
#出産生來後,放隊隊列
queue << i
puts "#{i} produced<---"
end
end
#消費者
consumer = Thread.new do
10.times do |i|
#為啥能實作???Excuse Me???
#在隊列Queue中pop時,如果沒有東西就一直阻塞等待着,直到queue中有東西出來後才繼續向下執行???
# 當能夠從隊列中,取出其生産的東西時候,就列印
# 如果隊列中pop不出東西的話,就會一直等待着
value = queue.pop
sleep rand(i/3)
puts " --->consume #{value}"
end
end
#join之後,主線程将阻塞
consumer.join
puts "主線程結束<---"
程式的輸出:
線程變量
線程可以有其私有變量,線程的私有變量線上程建立的時候寫入線程。
可以被線程範圍内使用,但是不能被線程外部進行共享。
但是有時候,線程的局部變量需要被别的線程或者主線程通路怎麼辦?
也不成問題,偉大的Ruby為我們提供了允許通過名字來建立線程變量,
就是類似的 把線程看做hash式的散清單, 通過[]=寫入 并通過 [] 讀出資料。
讓我們來看一下下面的代碼吧:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'thread'
count = 0
threadArr = []
#建立10個子線程
10.times do |i|
#把建立的線程用數組儲存起來
threadArr[i] = Thread.new{
#
sleep(rand(0)/10.0)
#
Thread.current['key'] = "#{Thread.current.to_s}"+"#{count}"
count = count +1
}
end
threadArr.each {|thread|
#阻塞主線程
thread.join;
#等子線程全部執行完了,再把每一個綁定到子線程中的值取出來給主線程列印
puts thread['key']
}
puts "count = #{count}"
以上代碼運作輸出結果為:
主線程等待子線程執行完成,然後分别輸出每個值。 。
線程優先級
線程的優先級 是影響線程的排程的主要因素。
其他因素包括 占用CPU的執行時間長短,線程分組排程 等等。
可以使用 Thread.priority 方法 得到 線程的優先級 和 使用 Thread.priority= 方法來 設定 線程的優先級。
線程的優先級預設為 0 。 優先級較高的執行的要快。
一個 Thread 可以通路自己作用域内的所有資料,
但如果有需要在某個線程内通路其他線程的資料應該怎麼做呢?
Thread 類提供了 線程間 資料互相通路 的方法,
你可以簡單的把一個線程作為一個 Hash 表,可以在任何線程内使用 []= 寫入資料,使用 [] 讀出資料。
代碼如下:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'thread'
thread_1 = Thread.new{
Thread.current['name'] = 'Thread_A'
Thread.stop
}
thread_2 = Thread.new{
Thread.current['name'] = 'Thread_B'
Thread.stop
}
thread_3 = Thread.new{
Thread.current['name'] = 'Thread_C'
Thread.stop
}
#列印目前的線程清單
Thread.list.each {|thread|
puts "#{thread.inspect}: #{thread['name']}"
}
運作效果如下:
可以看到,把線程作為一個 Hash 表,使用 [] 和 []= 方法,我們實作了線程之間的資料共享(???哪裡共享了???)。
線程互斥
Mutex(Mutal Exclusion = 互斥鎖)是一種用于多線程程式設計中,
防止兩條線程同時對同一公共資源(比如全局變量)進行讀寫的機制。
不使用Mutax的執行個體
代碼如下:
#!/usr/bin/ruby -w
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'thread'
count1 = count2 = 0
difference = 0
#循環累加的線程(Ruby中線程建立後自動執行)
Thread.new do
loop do
count1 += 1
count2 += 1
end
end
#讀值的線程(Ruby中線程建立後自動執行)
Thread.new do
loop do
#為啥會沒有差别呢??? 因為是同一行代碼,同時去讀兩個變量
difference += (count1 - count2).abs
end
end
#主線程去CUP通路的時候,讀取兩個數有執行的時間差,
#在這個時間差裡,後一個數已經比前一個數大太多啦
puts "count1:#{count1}"
puts "count2:#{count2}"
puts "difference:#{difference}"
以上執行個體運作輸出結果為:
下面是使用Mutax的執行個體
#!/usr/bin/ruby -w
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'thread'
#互斥鎖
mutex = Mutex.new
count1 = count2 = 0
difference = 0
#循環累加的線程(Ruby中線程建立後自動執行)
Thread.new do
loop do
#對累加代碼塊 加同步鎖
mutex.synchronize do
count1 += 1
count2 += 1
end
end
end
#讀值的線程(Ruby中線程建立後自動執行)
Thread.new do
loop do
#對讀值的代碼塊 加同步鎖
mutex.synchronize do
#為啥會沒有差别呢??? 因為是同一行代碼,同時去讀兩個變量
difference += (count1 - count2).abs
end
end
end
#CUP去通路的時候,讀取兩個數有執行的時間差
#在這個時間差裡,後一個數已經比前一個數大太多啦
puts "count1:#{count1}"
puts "count2:#{count2}"
puts "difference:#{difference}"
以上執行個體運作輸出結果為:
死鎖
兩個以上的運算單元,雙方都在等待對方停止運作,以擷取系統資源,但是沒有一方提前退出時,這種狀況,就稱為死鎖。
例如,一個程序 p1占用了顯示器,同時又必須使用列印機,而列印機被程序p2占用,p2又必須使用顯示器,這樣就形成了死鎖。
當我們在使用 Mutex 對象時需要注意線程死鎖。
執行個體
#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
cv = ConditionVariable.new
a = Thread.new {
mutex.synchronize {
puts "A: I have critical section, but will wait for cv"
cv.wait(mutex)
puts "A: I have critical section again! I rule!"
}
}
puts "(Later, back at the ranch...)"
b = Thread.new {
mutex.synchronize {
puts "B: Now I am critical, but am done with cv"
cv.signal
puts "B: I am still critical, finishing up"
}
}
a.join
b.join
以上執行個體輸出結果為:
A: I have critical section, but will wait for cv
(Later, back at the ranch...)
B: Now I am critical, but am done with cv
B: I am still critical, finishing up
A: I have critical section again! I rule!
線程類方法
完整的 Thread(線程) 類方法如下:
序号 | 方法描述 |
---|---|
1 | Thread.abort_on_exception 若其值為真的話,一旦某線程因異常而終止時,整個解釋器就會被中斷。它的預設值是假,也就是說,在通常情況下,若某線程發生異常且該異常未被Thread#join等檢測到時,該線程會被無警告地終止。 |
2 | Thread.abort_on_exception= 如果設定為 true, 一旦某線程因異常而終止時,整個解釋器就會被中斷。傳回新的狀态 |
3 | Thread.critical 傳回布爾值。 |
4 | Thread.critical= 當其值為true時,将不會進行線程切換。若目前線程挂起(stop)或有信号(signal)幹預時,其值将自動變為false。 |
5 | Thread.current 傳回目前運作中的線程(目前線程)。 |
6 | Thread.exit 終止目前線程的運作。傳回目前線程。若目前線程是唯一的一個線程時,将使用exit(0)來終止它的運作。 |
7 | Thread.fork { block } 與 Thread.new 一樣生成線程。 |
8 | Thread.kill( aThread ) 終止線程的運作. |
9 | Thread.list 傳回處于運作狀态或挂起狀态的活線程的數組。 |
10 | Thread.main 傳回主線程。 |
11 | Thread.new( [ arg ]* ) {| args | block } 生成線程,并開始執行。數會被原封不動地傳遞給塊. 這就可以在啟動線程的同時,将值傳遞給該線程所固有的局部變量。 |
12 | Thread.pass 将運作權交給其他線程. 它不會改變運作中的線程的狀态,而是将控制權交給其他可運作的線程(顯式的線程排程)。 |
13 | Thread.start( [ args ]* ) {| args | block } 生成線程,并開始執行。數會被原封不動地傳遞給塊. 這就可以在啟動線程的同時,将值傳遞給該線程所固有的局部變量。 |
14 | Thread.stop 将目前線程挂起,直到其他線程使用run方法再次喚醒該線程。 |
線程執行個體化方法
以下執行個體調用了線程執行個體化方法 join:
#!/usr/bin/ruby
thr = Thread.new do # 執行個體化
puts "In second thread"
raise "Raise exception"
end
thr.join # 調用執行個體化方法 join
以下是完整執行個體化方法清單:
序号 | 方法描述 |
---|---|
1 | thr[ name ] 取出線程内與name相對應的固有資料。 name可以是字元串或符号。 若沒有與name相對應的資料時, 傳回nil。 |
2 | thr[ name ] = 設定線程内name相對應的固有資料的值, name可以是字元串或符号。 若設為nil時, 将删除該線程内對應資料。 |
3 | thr.abort_on_exception 傳回布爾值。 |
4 | thr.abort_on_exception= 若其值為true的話,一旦某線程因異常而終止時,整個解釋器就會被中斷。 |
5 | thr.alive? 若線程是"活"的,就傳回true。 |
6 | thr.exit 終止線程的運作。傳回self。 |
7 | thr.join 挂起目前線程,直到self線程終止運作為止. 若self因異常而終止時, 将會目前線程引發同樣的異常。 |
8 | thr.key? 若與name相對應的線程固有資料已經被定義的話,就傳回true |
9 | thr.kill 類似于 Thread.exit 。 |
10 | thr.priority 傳回線程的優先度. 優先度的預設值為0. 該值越大則優先度越高. |
11 | thr.priority= 設定線程的優先度. 也可以将其設定為負數. |
12 | thr.raise( anException ) 在該線程内強行引發異常. |
13 | thr.run 重新啟動被挂起(stop)的線程. 與wakeup不同的是,它将立即進行線程的切換. 若對死程序使用該方法時, 将引發ThreadError異常. |
14 | thr.safe_level 傳回self 的安全等級. 目前線程的safe_level與$SAFE相同. |
15 | thr.status 使用字元串"run"、"sleep"或"aborting" 來表示活線程的狀态. 若某線程是正常終止的話,就傳回false. 若因異常而終止的話,就傳回nil。 |
16 | thr.stop? 若線程處于終止狀态(dead)或被挂起(stop)時,傳回true. |
17 | thr.value 一直等到self線程終止運作(等同于join)後,傳回該線程的塊的傳回值. 若線上程的運作過程中發生了異常, 就會再次引發該異常. |
18 | thr.wakeup 把被挂起(stop)的線程的狀态改為可執行狀态(run), 若對死線程執行該方法時,将會引發ThreadError異常。 |
Ruby RubyGems
RubyGems 是 Ruby 的一個包管理器,它提供一個分發 Ruby 程式和庫的标準格式,還提供一個管理程式包安裝的工具。
RubyGems 旨在友善地管理 gem 安裝的工具,以及用于分發 gem 的伺服器。
這類似于 Ubuntu 下的apt-get, Centos 的 yum,Python 的 pip。
RubyGems大約建立于2003年11月,從Ruby 1.9版起成為Ruby标準庫的一部分。
如果你的 Ruby 低于 1.9 版本,也可以通過手動安裝:
- 首先下載下傳安裝包:https://rubygems.org/pages/download。
- 解壓并進入目錄,執行指令:ruby setup.rb
更新 RubyGems 指令:
$ gem update --system # 需要管理者或root使用者
Gem
Gem 是 Ruby 子產品 (叫做 Gems) 的包管理器。其包含包資訊,以及用于安裝的檔案。
Gem通常是依照".gemspec"檔案建構的,包含了有關Gem資訊的YAML檔案。
附:YAML是"YAML Ain't a Markup Language"(YAML不是一種置智語言)的遞歸縮寫。
Ruby代碼也可以直接建立Gem,這種情況下通常利用Rake來進行。
附:Rake的意思是Ruby Make,一個用ruby開發的任務建構工具.
gem指令
gem指令用于建構、上傳、下載下傳以及安裝Gem包。
gem用法
RubyGems 在功能上與 apt-get、portage、yum 和 npm 非常相似。
安裝:
gem install beyondgem
解除安裝:
gem uninstall beyondgem
列出已安裝的gem:
bogon:~ beyond$ gem list --local
*** LOCAL GEMS ***
activesupport (4.2.6)
bigdecimal (1.2.0)
CFPropertyList (2.2.8)
claide (1.0.0)
cocoapods (1.0.1)
cocoapods-core (1.0.1)
cocoapods-deintegrate (1.0.0)
cocoapods-downloader (1.0.0)
cocoapods-plugins (1.0.0)
cocoapods-search (1.0.0)
cocoapods-stats (1.0.0)
cocoapods-trunk (1.0.0)
cocoapods-try (1.0.0)
colored (1.2)
escape (0.0.4)
fourflusher (0.3.1)
fuzzy_match (2.0.4)
i18n (0.7.0)
io-console (0.4.2)
json (1.7.7)
libxml-ruby (2.6.0)
minitest (5.9.0, 4.3.2)
molinillo (0.4.5)
nap (1.1.0)
netrc (0.7.8)
nokogiri (1.5.6)
psych (2.0.0)
rake (0.9.6)
rdoc (4.0.0)
sqlite3 (1.3.7)
test-unit (2.0.0.0)
thread_safe (0.3.5)
tzinfo (1.2.2)
xcodeproj (1.1.0)
列出所有可用的遠端gem,例如:
gem list --remote
為所有的gems建立RDoc文檔:
gem rdoc --all
下載下傳一個gem,但不安裝:
gem fetch beyondgem
從可用的gem中搜尋,例如:
gem search STRING --remote
Ruby_11_多線程と包管理
gem 包的建構
gem指令也被用來建構和維護.gemspec和.gem檔案。
利用.gemspec檔案建構.gem:
gem build beyondgem.gemspec
修改國内源
由于國内網絡速度不佳,導緻 rubygems.org 存放在 Amazon S3 上面的資源檔案間歇性連接配接失敗。
是以你會與遇到 gem install rack 或 bundle install 的時候半天沒有響應,
具體可以用 gem install rails -V 來檢視執行的過程。
是以我們可以将它修改為淘寶下載下傳源: http://ruby.taobao.org/
首先,檢視目前源:
$ gem sources -l
*** CURRENT SOURCES ***
https://rubygems.org/
接着,移除https://rubygems.org/,
并添加淘寶下載下傳源 http://ruby.taobao.org/。
$ gem sources --remove https://rubygems.org/
$ gem sources -a https://ruby.taobao.org/
$ gem sources -l
*** CURRENT SOURCES ***
https://ruby.taobao.org
# 請確定隻有 ruby.taobao.org
$ gem install rails
Ruby_11_多線程と包管理
如果你使用 Gemfile 和 Bundle (例如:Rails 項目) ???Excuse Me???
你可以用bundle的gem源代碼鏡像指令。
$ bundle config mirror.https://rubygems.org https://ruby.taobao.org
這樣你不用改你的 Gemfile 的 source。
source 'https://rubygems.org/'
gem 'rails', '4.1.0'
...
未完待續,下一章節,つづく