Ruby 線程(二)

來源:互聯網
上載者:User

6、Using a Thread Group

線程組是管理線程的一種方式,它將線程彼此從邏輯上關聯起來。通常,所有線程屬於Default線程組(它是個類常量)。但如果建立了一個新線程組,則新線程會被添加到其中。

一個線程每次只可屬於一個線程組。當線程被添加到線程組時,它自動地被從它先前的線程組中移出。

ThreadGroup.new類方法建立一個新線程組,然後adds執行個體方法添加線程到組內:

f1thread = Thread.new("file1") { |file| waitfor(file) }

f2thread = Thread.new("file2") { |file| waitfor(file) }

file_threads = ThreadGroup.new

file_threads.add f1

file_threads.add f2

# Count living threads in this_group

count = 0

this_group.list.each { |x| count += 1 if x.alive? }

if count < this_group.list.size

puts "Some threads in this_group are not living."

else

puts "All threads in this_group are alive."

end

有很多有用的方法被添加給ThreadGroup。這兒我們顯示的方法喚醒組內的每個線程,等待捕獲所有線程(通過join),殺死組內所有線程:

class ThreadGroup

def wakeup

list.each { |t| t.wakeup }

end

def join

list.each { |t| t.join if t != Thread.current }

end

def kill

list.each { |t| t.kill }

end

end

二、Synchronizing Threads

為什麼同步是必須的?這是因為操作的交錯引起變數和其它實體,在不明顯地被從不同線程的讀代碼的訪問方式。兩個或更多線程訪問同一變數可以彼此互相影響,這種方式是無法預料和調試困難的。

讓我們看看這個例子的代碼片斷:

x = 0

t1 = Thread.new do

1.upto(1000) do

x = x + 1

end

end

t2 = Thread.new do

1.upto(1000) do

x = x + 1

end

end

t1.join

t2.join

puts x

變數x開始時為0。每個線程1000秒增加它一次。邏輯告訴我們輸出時x必須是2000。

但是我們這兒有什嗎?在一個特定系統上,它列印1044做為結果。哪兒有錯誤?

我們代碼假設一個整數的增加操作是原子的(或不可分割的)操作。但是它不是。考慮下面邏輯流程。我們放置線程t1在左邊,t2在右邊。每個行是一個單獨的時間片,我們假設在進入這個邏輯片時,x的值是123。

t1 線程 t2 線程

__________________________ __________________________

擷取x的值(123)

擷取x的值 (123)

將值加1 (124)

將值加1 (124)

儲存結果到x內

儲存結果到x內。

很明顯,每個線程都從它自己的視點來完成簡單的增量操作。這種情況下,同樣明顯的是在兩個線程執行完增量後x只有124。

這隻是簡單的同步問題。最壞的部分會變得更難於管理,並且成為電腦學家和數學家研究的真正對象。

1、用臨界區完成簡單同步

簡單的同步形式是使用臨界區。當線程進入代碼的臨界區時,這個技術保證沒有其它線程將被運行直到第一個線程離開它的臨界區。

Thread.critical存取器,當設定為true時,將阻止其它線程被調度。這兒們看個例子,我們只討論和使用這個技術來修正它。

x = 0

t1 = Thread.new do

1.upto(1000) do

Thread.critical = true

x = x + 1

Thread.critical = false

end

end

t2 = Thread.new do

1.upto(1000) do

Thread.critical = true

x = x + 1

Thread.critical = false

end

end

t1.join

t2.join

puts x

現在邏輯流程被強迫成類似下面。(當然,在增量部分的外面,線程是自由地或多或少隨機地交錯操作。)

t1 線程 t2 線程

__________________________ __________________________

取出x的值(123)

增量操作(124)

儲存結果回x內

取出x的值 (124)

增量操作(125)

儲存結果回x內

線程管理和完成操作的結合是可能的,這會引起一個線程被調度,即使另一個線程在臨界區中。在最簡單情況下,新建立的線程將立即運行,而不管另一個線程是否在臨界區中。因此,這個技術應該只被用在最簡單的環境中。

2、對資源同步訪問 (mutex.rb)

讓我們拿一個Web索引應用程式做為例子。我們在網路上的多個源中取出單詞並且儲存它們到一個雜湊表內。單詞本身將被做為鍵,而值是識別文檔及文檔內行號的字串。

這是個非常粗糙的例子。但是出於簡單的理由我們讓它更粗糙:

1. 我們將遠程文檔描述成簡單字串。

2. 我們將它限制為三個字串(簡單的寫入程式碼資料)。

3. 我們用隨機睡眠模仿網路訪問的變化。

那麼,讓我們來看看Listing7.1。它甚至不列印它收集的資料,並且只有一個被找到單詞數的count。注意每當雜湊表被檢查或更改時,我們調用hesitate方法來睡眠隨機間隔。這會讓程式運行在更不確定和更現實的方式上。

Listing 7.1 Flawed Indexing Example (with a Race Condition)

$list = []

$list[0]="shoes shipsnsealing-wax"

$list[1]="cabbages kings"

$list[2]="quarksnshipsncabbages"

 

 

def hesitate

sleep rand(0)

end

$hash = {}

def process_list(listnum)

lnum = 0

$list[listnum].each do |line|

words = line.chomp.split

words.each do |w|

hesitate

if $hash[w]

hesitate

$hash[w] += ["#{ listnum} :#{ lnum} "]

else

hesitate

$hash[w] = ["#{ listnum{ :#{ lnum} "]

end

end

lnum += 1

end

end

t1 = Thread.new(0) { |list| process_list(list) }

t2 = Thread.new(1) { |list| process_list(list) }

t3 = Thread.new(2) { |list| process_list(list) }

t1.join

t2.join

t3.join

 

 

count = 0

$hash.values.each do |v|

count += v.size

end

puts "Total: #{ count} words" # May print 7 or 8!

但是有個問題。如果你的系統行為與我們的一樣,這兒是程式可能輸出的兩個數字!在我們的測試中,它近似相等地列印7和8。在有更多單詞的情況下,會有更大的變化。

讓我們試試用互斥來修正它,互斥用於控制共用資源的訪問。(當然,這個術語來自於單詞mutual exclusion。)Mutex庫將允許我們建立和操縱一個mutex。當我們準備訪問雜湊表時,我們可以鎖住它,當我們完成時,我們解鎖它(參見Listing7.2)。

Listing 7.2 Mutex Protected Indexing Example

require "thread.rb"

 

 

$list = []

$list[0]="shoes shipsnsealing-wax"

$list[1]="cabbages kings"

$list[2]="quarksnshipsncabbages"

def hesitate

sleep rand(0)

end

$hash = {}

$mutex = Mutex.new

def process_list(listnum)

lnum = 0

$list[listnum].each do |line|

words = line.chomp.split

words.each do |w|

hesitate

$mutex.lock

if $hash[w]

hesitate

$hash[w] += ["#{ listnum} :#{ lnum} "]

else

hesitate

$hash[w] = ["#{ listnum{ :#{ lnum} "]

end

$mutex.unlock

end

lnum += 1

end

end

t1 = Thread.new(0) { |list| process_list(list) }

t2 = Thread.new(1) { |list| process_list(list) }

t3 = Thread.new(2) { |list| process_list(list) }

t1.join

t2.join

t3.join

count = 0

$hash.values.each do |v|

count += v.size

end

puts "Total: #{ count} words" # Always prints 8!

我們應該提一下除了lock外,Mutex類也有try_lock方法。它的行為類似於lock,除了當另一個線程已經鎖時,它將直接返回false而不等待。

$mutex = Mutex.new

t1 = Thread.new { $mutex.lock; sleep 30 }

 

 

sleep 1

t2 = Thread.new do

if $mutex.try_lock

puts "Locked it"

else

puts "Could not lock" # Prints immediately

end

end

sleep 2

無論何時,一個線程不想被鎖住時,這個特徵是非常有用的。

 

 

3、使用預定義同步的Queue

線程庫thread.rb有幾個有時很有用的類。類Queue是同步訪問隊列末端的線程敏感的隊列;也就是說,不同的線程可以使用同一隊列,而不會出現問題。SizedQueue類本質一樣的,除了它允許限制隊列的大小(隊列可包含的元素數量)。

有很多相似的方法,因為SizedQueue實際上繼承了Queue。匯出類也有存取器max來用或set隊列最大尺寸。

buff = SizedQueue.new(25)

upper1 = buff.max # 25

# Now raise it...

buff.max = 50

upper2 = buff.max # 50

Listing7.3顯示了一個簡單的生產者-消費者示範。消費者以平均時間(通過一個很長的睡眠時間)被顯示,以便條目的收集。

Listing 7.3 The Producer-Consumer Problem

require "thread"

 

 

buffer = SizedQueue.new(2)

producer = Thread.new do

item = 0

loop do

sleep rand 0

puts "Producer makes #{ item} "

buffer.enq item

item += 1

end

end

 

 

consumer = Thread.new do

loop do

sleep (rand 0)+0.9

item = buffer.deq

puts "Consumer retrieves #{ item} "

puts " waiting = #{ buffer.num_waiting} "

end

end

sleep 60 # Run a minute, then die and kill threads

方法enq和deq是用於擷取隊列的進出條目的被推薦方式。我們也可以使用push來添加條目到隊列,用pop或shift從隊列移出條目,但是當我們明確使用隊列時,這些名字有點缺少記憶價值。

方法empty?測試空隊列,clear方法清空隊列。方法size(別名length)返回隊列內實際條目數量。

# Assume no other threads interfering...

buff = Queue.new

buff.enq "one"

buff.enq "two"

buff.enq "three"

n1 = buff.size # 3

flag1 = buff.empty? # false

buff.clear

n2 = buff.size # 0

flag2 = buff.empty? # true

num_waiting方法是等待訪問隊列的線程數量。在沒有指定大小的隊列中,是等待移除元素的線程的數量;在指定大小的隊列中,同樣是等待添加元素到隊列的線程數量。

Queue類內的deq方法有選擇性參數non_block,預設值是false。如果它為true,一個空隊列會產生ThreadError錯誤而不是鎖住線程。

 

 

4、使用條件變數

And he called for his fiddlers three.

"Old King Cole" (traditional folk tune)

條件變數是個真正的線程隊列。它與mutex一同使用,在同步線程時提供進階別的控制。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.