人妖在线一区,国产日韩欧美一区二区综合在线,国产啪精品视频网站免费,欧美内射深插日本少妇

新聞動(dòng)態(tài)

Ruby多線程庫(kù)(Thread)使用方法詳解

發(fā)布日期:2022-07-15 19:06 | 文章來(lái)源:CSDN

Thread是Ruby的線程庫(kù),Thread庫(kù)已經(jīng)內(nèi)置在Ruby中,但如果想要使用線程安全的Queue、Mutex以及條件變量等,則需要手動(dòng)require 'thread'。

主線程main

默認(rèn)情況下,每個(gè)Ruby進(jìn)程都具備一個(gè)主線程main,如果沒(méi)有創(chuàng)建新的線程,所有的代碼都將在這個(gè)主線程分支中執(zhí)行。

使用Thread.main()類方法可獲取當(dāng)前線程組的主線程,使用Thread.current()可以獲取當(dāng)前正在執(zhí)行的線程分支。使用Thread.list()可獲取當(dāng)前進(jìn)程組中所有存活的線程。

p Thread.main
p Thread.current
p Thread.main == Thread.current
=begin
#<Thread:0x0000000001d9ae58 run>
#<Thread:0x0000000001d9ae58 run>
true
=end

可見(jiàn),線程其實(shí)是一個(gè)Thread類的實(shí)例對(duì)象。

創(chuàng)建Ruby線程

使用Thread庫(kù)的new()、start()、fork()可創(chuàng)建線程,它們幾乎等價(jià),且后兩者是別名關(guān)系。

創(chuàng)建線程時(shí)需傳遞一個(gè)代碼塊或Proc對(duì)象參數(shù), 它們是要執(zhí)行的任務(wù),它們將在新的線程分支中執(zhí)行。如果需要,可以為代碼塊或Proc對(duì)象傳遞參數(shù)。

arr=[]
a,b,C=1,2,3
Thread.new(a,b,c) { |d,e,f| arr << d << e << f }
sleep 1
p arr#=> [1,2,3]

如果主線程先執(zhí)行完成,主線程將直接退出,主線程的退出將會(huì)終止進(jìn)程,使得其它線程也會(huì)退出。

Thread.new {puts "hello"}
puts "world"

上述代碼幾乎總是會(huì)輸出world,然后退出,主線程的退出使得子線程不會(huì)輸出"hello"。之所以總是會(huì)輸出world而不是輸出hello,這和Ruby的線程調(diào)度有關(guān),在后面的文章中會(huì)詳細(xì)解釋Ruby中的線程調(diào)度。

join()和value()等待線程

如果想要等待某個(gè)線程先執(zhí)行完成,可使用t.join(),如果線程t尚未退出,則join()會(huì)阻塞??梢栽谌我饩€程中調(diào)用t.join(),誰(shuí)調(diào)用誰(shuí)等待。

t = Thread.new { puts "I am Child" }
t.join  # 等待子線程執(zhí)行完成
puts "I am Parent"

還可以將多個(gè)線程對(duì)象放進(jìn)數(shù)組,然后執(zhí)行遍歷join,另一種常見(jiàn)的做法是使用map{}.each(&:join)的方式:

threads = []
3.times do |i|
  # 將多個(gè)線程加入到數(shù)組中
  threads << Thread.new { puts "Thread #{i}" }
end
# 在main線程中join每個(gè)線程,
# 因此只有3個(gè)線程全都完成后,main線程才會(huì)繼續(xù),即退出
threads.each(&:join)
=begin
Thread 1
Thread 0
Thread 2
=end
# 另一種常見(jiàn)方式
3.times.map {|i| Thread.new { puts "Thread #{i}" } }.each(&:join)
Array.new(3) {|i| Thread.new { puts "Thread #{i}" } }.each(&:join)

t.value()t.join()類似,不同之處在于t.value()在內(nèi)部調(diào)用t.join()等待線程t之后,還會(huì)在等待成功時(shí)取得該線程的返回值。

a = Thread.new { 2 + 2 }
p a.value#=> 4

注意,對(duì)于Ruby來(lái)說(shuō),無(wú)論是否執(zhí)行join()操作,任務(wù)執(zhí)行完成的線程都會(huì)馬上被操作系統(tǒng)回收(從OS線程表中刪除),但被回收的線程仍然能夠使用value()方法來(lái)獲取被回收線程的返回值。之所以會(huì)這樣,我個(gè)人猜想,也許是因?yàn)镽uby內(nèi)部已經(jīng)幫我們執(zhí)行了join操作并將線程返回值保存在Ruby內(nèi)部,這樣對(duì)于用戶來(lái)說(shuō)就更加安全,而且用戶執(zhí)行join()或value()操作,可能是在等待Ruby內(nèi)部的這個(gè)值的出現(xiàn)。

線程的異常處理

默認(rèn)情況下,當(dāng)某個(gè)非main線程中拋出異常后,該線程將因異常而終止,但是它的終止不會(huì)影響其它線程。

t = Thread.new {raise "hello"} # 拋出異常
sleep 1 # 仍然睡眠1秒后退出

如果使用了t.join()t.value()去等待拋出異常的線程t,異常將會(huì)傳播給調(diào)用這兩個(gè)方法的線程。例如主線程調(diào)用t.join,如果t會(huì)拋出一次異常,那么主線程在等待過(guò)程中還會(huì)拋出一次異常。

t = Thread.new {raise "hello"} # 拋出異常
t.join() # 子線程拋異常后,main線程也拋異常

如果想要讓任意線程出現(xiàn)異常時(shí)終止整個(gè)程序,可設(shè)置類方法Thread.abort_on_exception為true,它會(huì)在任意子線程拋出異常后自動(dòng)傳播給main線程,從而終止進(jìn)程:

Thread.abort_on_exception = true
Thread.new { raise "Error" }
sleep 1# 不會(huì)睡眠完1秒,而是子線程異常后立即異常退出

如果想要讓某個(gè)特定的線程出現(xiàn)異常時(shí)終止整個(gè)程序,可設(shè)置同名的實(shí)例方法t.abort_on_exception為true,只有t線程異常時(shí)才會(huì)終止程序。

t1 = Thread.new { raise "Error from t1" }
t1.abort_on_exception = true
sleep 1

另外,線程實(shí)例方法t.raise()可以直接在線程t拋出異常。

需注意,Ruby線程有一個(gè)巨大的缺點(diǎn):無(wú)論是raise拋出異常還是各種終止(比如kill、exit),都不會(huì)執(zhí)行ensure子句。

線程的狀態(tài)和生命周期

Ruby中的線程具有5種狀態(tài),可通過(guò)t.status()查看,該方法有5種對(duì)應(yīng)的返回值:

- run: 線程正在運(yùn)行(running)或可運(yùn)行(runnable)  
- sleep: 線程處于睡眠態(tài),比如阻塞(如sleep,mutex,io block)  
- false: 線程正常退出后的狀態(tài),包括執(zhí)行完流程、手動(dòng)退出(t.exit)、信號(hào)終止(t.kill)  
- nil: 線程因拋出異常(比如raise)而退出的狀態(tài)  
- aborting: 線程被完全kill之前的過(guò)渡狀態(tài),不考慮這種狀態(tài)的存在

另外,還有兩種統(tǒng)稱狀態(tài):

  • alive:存活的線程,等價(jià)于run + sleep
  • stop:已停止的線程,等價(jià)于sleep + dead(false+nil)

可分別使用alive?()stop?()來(lái)判斷線程是否屬于這兩種統(tǒng)稱狀態(tài)。

此外:

Kernel.sleep:讓當(dāng)前線程睡眠指定時(shí)長(zhǎng),無(wú)參數(shù)則永久睡眠,線程將進(jìn)入睡眠隊(duì)列
Thread.stop:讓當(dāng)前線程睡眠,進(jìn)入睡眠隊(duì)列,等價(jià)于無(wú)參數(shù)的sleep  
Thread.pass:轉(zhuǎn)讓CPU,當(dāng)前線程進(jìn)入就緒隊(duì)列而不是睡眠隊(duì)列  
t.run:?jiǎn)拘丫€程t使其進(jìn)入就緒隊(duì)列,同時(shí)讓當(dāng)前線程放棄CPU,調(diào)度程序?qū)⒅匦抡{(diào)度  
t.wakeup:?jiǎn)拘丫€程t使其進(jìn)入就緒隊(duì)列,但不會(huì)讓當(dāng)前線程放棄CPU,調(diào)度程序?qū)⒉粫?huì)立即重新調(diào)度  
Thread.kill:終止指定線程,它將不再被調(diào)度  
Thread.exit:終止當(dāng)前線程,它將不再被調(diào)度  
t.exit,t.kill,t.terminate:終止線程t,t將不再被調(diào)度

幾個(gè)注意事項(xiàng):

  • 這里5個(gè)終止線程的方式效果上是完全等價(jià)的,三個(gè)實(shí)例方法是別名關(guān)系,而兩個(gè)類方法的內(nèi)部也都是調(diào)用線程對(duì)象的kill
  • 最好要不加區(qū)分地看待run和wakeup
  • 對(duì)于Thread.pass,除了知道它轉(zhuǎn)讓CPU的行為是確定的,不要對(duì)它假設(shè)任何額外的行為,比如不要認(rèn)為出讓CPU后一定會(huì)調(diào)度到其它Ruby線程,很有可能會(huì)在調(diào)度其它一些非Ruby線程后再次先調(diào)度到本線程而非其它Ruby線程
  • 需注意,無(wú)論是raise拋出異常還是各種終止(比如kill、exit),都不會(huì)執(zhí)行ensure子句

線程私有變量和局部變量

Ruby進(jìn)程內(nèi)的所有線程共享進(jìn)程的虛擬地址空間,所以共享了一些數(shù)據(jù)。

但線程是語(yǔ)句塊或者Proc對(duì)象,所以語(yǔ)句塊內(nèi)部創(chuàng)建的變量是在當(dāng)前線程棧內(nèi)部的,是每個(gè)線程私有的變量。

# 主線程中的變量
a = 1
# 子線程
t1 = Thread.new(3) do |x|
  a += 1
  b=3
  x=4
end
# 主線程
t1.join
p a# 2
#p b  # 報(bào)錯(cuò),b不存在
#p x  # 報(bào)錯(cuò),x不存在

Ruby為線程提供了局部變量共享的概念,每個(gè)線程對(duì)象都可以有自己的局部數(shù)據(jù)空間(即線程本地變量),線程對(duì)象的局部空間互不影響,比如兩個(gè)線程中同時(shí)進(jìn)行正則匹配,兩個(gè)線程的$~是不一樣且互不影響的。

線程對(duì)象t的局部數(shù)據(jù)空間是t[key]=value,即一個(gè)名為t的hash結(jié)構(gòu),因?yàn)閷?duì)象t是可以共享的,所以它的局部空間也是共享的。

t1 = Thread.new do
  t = Thread.current
  t[:name] = "junmajinlong"
  t[:age] = 23
end
t1.join
p t1.keys # [:name, :age]
p t1.key? :gender  # false
p t1[:name]  # "junmajinlong"
t1[:age] = 24  
p t1[:age]# 24

所以,有這么幾個(gè)方法:

t[key]
t[key]=
t.keys
t.key?

此外還有一個(gè)fetch()方法,類似于Hash的fetch(),默認(rèn)情況下訪問(wèn)不存在的key會(huì)異常,可指定默認(rèn)值或通過(guò)語(yǔ)句塊返回默認(rèn)值。

嚴(yán)格來(lái)說(shuō),從Ruby 1.9出現(xiàn)Fiber之后,t[]不再是線程本地變量(thread-local),而是纖程(Fiber)本地變量(fiber-local)。但也支持使用線程本地變量:

t.thread_variables
t.thread_variable?
t.thread_variable_get
t.thread_variable_set

線程組

默認(rèn)情況下,所有線程都在默認(rèn)的線程組中,這個(gè)默認(rèn)線程組是Ruby程序啟動(dòng)時(shí)創(chuàng)建的??墒褂?code>ThreadGroup::Default獲取默認(rèn)線程組。

t1 = Thread.new do
  Thread.stop
end
p t1.group
p Thread.current.group
p ThreadGroup::Default
=begin
#<ThreadGroup:0x00000000019bcb60>
#<ThreadGroup:0x00000000019bcb60>
#<ThreadGroup:0x00000000019bcb60>
=end
  • 使用ThreadGroup.new可創(chuàng)建一個(gè)自定義的線程組
  • 使用tg.add(t)可將線程t加入線程組tg,這將會(huì)從原來(lái)的線程組移除t再加入新組tg
  • 使用tg.list可列出線程組tg中的所有線程
  • 使用t.group可獲取線程t所屬的線程組
  • 子線程會(huì)繼承父線程的線程組,即子線程也會(huì)加入父線程所在的線程組
tg = ThreadGroup.new
t1 = Thread.new { Thread.stop }
t2 = Thread.new { Thread.stop }
tg.add t1
tg.add t2
pp tg.list
pp t1.group
=begin
[#<Thread:0x000000000196c480 a.rb:4 sleep_forever>,
 #<Thread:0x000000000196c3b8 a.rb:5 sleep_forever>]
#<ThreadGroup:0x000000000196c520>
=end

線程組還有一個(gè)功能:可使用tg.enclose封閉線程組tg,封閉后的線程組將不允許內(nèi)部線程移出加入其它組,也不允許外界線程加入該組,只允許在該組中創(chuàng)建新線程。使用tg.enclosed?測(cè)試線程組tg是否已封閉。

其實(shí),使用線程組可以將多個(gè)線程分類統(tǒng)一管理,線程組本質(zhì)是一個(gè)線程數(shù)組加一些額外屬性。比如,可以為線程組定義一些額外的針對(duì)線程組中所有線程的功能:wakeup組中的所有線程、join所有線程、kill所有線程。

class ThreadGroup
  def wakeup
 list.each(&:wakeup)
  end
  def join
 list.each { |th| th.join if th != Thread.current }
  end
  def kill
 list.each(&:kill)
  end
end

更多關(guān)于Ruby多線程知識(shí)請(qǐng)查看下面的相關(guān)鏈接

香港服務(wù)器租用

版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。

相關(guān)文章

實(shí)時(shí)開(kāi)通

自選配置、實(shí)時(shí)開(kāi)通

免備案

全球線路精選!

全天候客戶服務(wù)

7x24全年不間斷在線

專屬顧問(wèn)服務(wù)

1對(duì)1客戶咨詢顧問(wèn)

在線
客服

在線客服:7*24小時(shí)在線

客服
熱線

400-630-3752
7*24小時(shí)客服服務(wù)熱線

關(guān)注
微信

關(guān)注官方微信
頂部