Ruby 線程(三)

來源:互聯網
上載者:User

5、使用其它同步技術

另一個同步機制是監視器,Ruby在monitor.rb庫中實現。這個技術比互斥要更進階;特別是互斥鎖不可以被嵌套,但監聽器鎖可以。

有些瑣細的事從未發生過。那是因為沒人會像下面這樣寫:

$mutex = Mutex.new

$mutex.synchronize do

$mutex.synchronize do

#...

end

end

但是它也許會發生(或通過一個遞迴調用)。在任何這些情況下的結果是死結。避免這種情形下的死結是混插Monitor的優勢。

$mutex = Mutex.new

def some_method

$mutex.synchronize do

#...

some_other_method # Deadlock!

end

end

def some_other_method

$mutex.synchronize do

#...

end

end

Monitor mixin被典型地用於擴充任何對象。new_cond方法可以用於執行個體化一個條件變數。

ConditionVariable類來自於第三方庫是對monitor.rb的增強。它有方法wait_until和wait_while,它的塊是基於條件的線程。 which block a thread based on a condition.它也允許在等待時的暫停,因為wait方法有個timeout參數,它是個秒數(預設為nil)。

因為我們快速地用完線程的例子,我們拿出Listing7.5內使用監視器技術重寫Queue和SizedQueue類。代碼由Shugo Maeda寫出,並被許可使用。

Listing 7.5 Implementing a Queue with a Monitor

# Author: Shugo Maeda

require "monitor"

 

 

class Queue

def initialize

@que = []

@monitor = Monitor.new

@empty_cond = @monitor.new_cond

end

def enq(obj)

@monitor.synchronize do

@que.push(obj)

@empty_cond.signal

end

end

def deq

@monitor.synchronize do

while @que.empty?

@empty_cond.wait

end

return @que.shift

end

end

end

class SizedQueue < Queue

attr :max

 

 

def initialize(max)

super()

@max = max

@full_cond = @monitor.new_cond

end

def enq(obj)

@monitor.synchronize do

while @que.length >= @max

@full_cond.wait

end

super(obj)

end

end

def deq

@monitor.synchronize do

obj = super

if @que.length < @max

@full_cond.signal

end

return obj

end

end

def max=(max)

@monitor.synchronize do

@max = max

@full_cond.broadcast

end

end

end

sync.rb 庫以更多方式來完成線程的步。對於我們知道和關心的事情,它用一個counter實現了二相鎖。在寫本書時,它的唯一文件就是庫本身。

 

 

6、Allowing Timeout of an Operation

在很多情況下,我們需要有允許完成一個動作的最長時間。這可避免死迴圈並允許在處理上有額外的控制層。這對網路環境下是個有用的特徵,在其它環境下,我們可能或者不可能從遠程伺服器上得到響應。

timeout.rb庫以基於線程的解決辦法處理這個問題。Timeout方法執行寫方法調用關聯的塊;當到達指定秒數時,它拋出TimeoutError錯誤,可以用rescue子句捕獲它(見Listing7.6)。

Listing 7.6 A Timeout Example

require "timeout.rb"

 

 

flag = false

answer = nil

begin

timeout(5) do

puts "I want a cookie!"

answer = gets.chomp

flag = true

end

rescue TimeoutError

flag = false

end

if flag

if answer == "cookie"

puts "Thank you! Chomp, chomp, ..."

else

puts "That's not a cookie!"

exit

end

else

puts "Hey, too slow!"

exit

end

puts "Bye now..."

 

7、等待事件

在很多情況下,我們想在其它線程完成其它事情時,從外部監視一個或多個線程。這兒例子是人為的,但它示範通用原則。

這兒,們看到三個線程完成一個應用程式的工作。另一個線程每五秒被簡單地喚醒,檢查全域變數$flag,當它看到這個flag設定時,它喚醒其它三個線程。它儲存三個背景工作執行緒直接與其它二個線程互動,並且試圖喚醒它們。

$job = false

work1 = Thread.new { job1() }

work2 = Thread.new { job2() }

work3 = Thread.new { job3() }

 

 

thread5 = Thread.new { Thread.stop; job4() }

thread6 = Thread.new { Thread.stop; job5() }

watcher = Thread.new do

loop do

sleep 5

if $flag

thread5.wakeup

thread6.wakeup

Thread.exit

end

end

end

在job方法的運行期間任何時候若變數$flag變成true,thread5和thread6保證在五秒內啟動。在這之後,watcher線程終止。

下個例子中,我們等待檔案被建立。我們每三十秒檢查一次,如果看見了就啟動另一個線程;在此期間,其它線程可以做任何事。實際上,這兒我們在分別在觀察三個檔案。

def waitfor(filename)

loop do

if File.exist? filename

file_processor = Thread.new do

process_file(filename)

end

Thread.exit

else

sleep 30

end

end

end

waiter1 = Thread.new { waitfor("Godot") }

sleep 10

waiter2 = Thread.new { waitfor("Guffman") }

sleep 10

headwaiter = Thread.new { waitfor("head") }

# Main thread goes off to do other things...

還有很多其它情況,線程會等待一個外來事件 ,如網路應用程式,伺服器端的socket慢或不可靠。

 

 

8、Continuing Processing During I/O

一個應用程式經常地有一個或多個冗長的或費時I/O操作。在有使用者輸入的情況下就會這樣,因為使用者在鍵盤上的輸入甚至比磁碟操作都很慢。我們可以通過線程來使用這段時間。

考慮國際像棋的例子,它必須等待人移動它。當然,我們只表示這個概念的架構。

我們假設迭代器predictMove將重複地產生相似的人們可能做出的移動(然後確定程式員對這些移動的自己的響應)。然後當人們移動時,它可能已經準備好預期的移動了。

scenario = { } # move-response hash

humans_turn = true

thinking_ahead = Thread.new(board) do

predictMove do |m|

scenario[m] = myResponse(board,m)

Thread.exit if humans_turn == false

end

end

human_move = getHumanMove(board)

humans_turn = false # Stop the thread gracefully

# Now we can access scenario which may contain the

# move the person just made...

我們必須做出聲明,真正的像棋程式通常不使用這種方式工作。通常關心的是快速搜尋和通過一定的深度;在現實生活中,最好的解決辦法是在thinking線程期間儲存擷取的部分狀態資訊,然後以同樣方式繼續直到程式找到一個好的響應或超出它的時間。

9、實現並行迭代

假設你想在超過一個的對象上並行迭代。也就是說,n個對象中的每一個,你想迭代第一個,第二個,第三個等等。

為做得更具體一些,看看下面例子。這兒我們假設compose是提供迭代器組成的方法的名字。我們也假設每個特定對象有個被使用的預設迭代器each,每個對象每次提出個條目。

arr1 = [1, 2, 3, 4]

arr2 = [5, 10, 15, 20]

compose(arr1, arr2) do |a,b|

puts "#{ a} and #{ b} "

end

# Should output:

# 1 and 5

# 2 and 10

# 3 and 15

# 4 and 20

我們能採用更有思想的方式,在每個對象上完成迭代,一個接一個,儲存結果。但是如果我們想要更優美的解決辦法,實際上是不儲存所有條目,線程是唯一容易的解決辦法。我們的答案在Listing7.7中。

Listing 7.7 Iterating in Parallel

def compose(*objects)

threads = []

for obj in objects do

threads << Thread.new(obj) do |myobj|

me = Thread.current

me[:queue] = []

myobj.each do |element|

me[:queue].push element

end

end

end

list = [0] # Dummy non-nil value

while list.nitems > 0 do # Still some non-nils

list = []

for thr in threads

list << thr[:queue].shift # Remove one from each

end

yield list if list.nitems > 0 # Don't yield all nils

end

end

x = [1, 2, 3, 4, 5, 6, 7, 8]

y = " firstn secondn thirdn fourthn fifthn"

z = %w[a b c d e f]

compose(x, y, z) do |a,b,c|

p [a, b, c]

end

# Output:

#

# [1, " firstn", "a"]

# [2, " secondn", "b"]

# [3, " thirdn", "c"]

# [4, " fourthn", "d"]

# [5, " fifthn", "e"]

# [6, nil, "f"]

# [7, nil, nil]

# [8, nil, nil]

注意我們沒有假設所有對象在迭代時有相同數量的條目。如果一個迭代器在其它迭代器之前運行完,它將產生nil值直到最長的迭代器運行完畢。

當然,可以寫更通用的方法來從每個迭代器中抓取多於一個的值。(畢竟,不是所有迭代器都每次只返回一個值。)我們可以讓第一個參數指定每次迭代的數量。

使用任意迭代器(而不是預設的each)也是可行的。我們可以傳遞它們的名字做為字串,並使用send來調用它們。當然這需要其它竅門來完成。

然而,我們認為這兒給出的例子對大多數情形足夠了。我們將其它的變化留給你做為練習。

 

 

10、並行化的遞迴刪除

只是出於樂趣,讓我們使用第四章中的"External Data Manipulation"的例子並且並行化它。(沒有,我們不是說平行地使用多個處理器。)這兒以線程形式給出遞迴刪除常式。當我們找到的目錄條目本身是個目錄時,我們啟動新線程來遍曆那個目錄並刪除它的內容。

我們儲存我們建立的線程的跟蹤在稱為threads的數組內;因為它是局部變數,每個線程都有它自己的那個數組的拷貝。它每次只可以由一個線程訪問,這兒不需要對它同步訪問。

注意我們也傳遞全檔案名稱給線程塊,以便我們不必為線程訪問了一個可修改的值而煩心。線程使用fn做為同一變數的本地拷貝。

當我們遍曆一個目錄時,我們想在刪除我們已完成工作的目錄之前等待我們建立的線程。

def delete_all(dir)

threads = []

Dir.foreach(dir) do |e|

# Don't bother with . and ..

next if [".",".."].include? e

fullname = dir + File::Separator + e

if FileTest::directory?(fullname)

threads << Thread.new(fullname) do |fn|

delete_all(fn)

end

else

File.delete(fullname)

end

end

threads.each { |t| t.join }

Dir.delete(dir)

end

delete_all("/tmp/stuff")

它比非線程版本實際上快嗎?我們發現回覆不一致。它取決於你的作業系統和實際被刪除的目錄結構,即,它的深度,檔案的大小等等。

 

 


三、總結

在很多情況下線程是很有用的技術,但它們對代碼和調試可能有些問題。當我們使用同步方法來達到正確的結果時,這是真實的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.