天天看點

Ruby_11_多線程と包管理

Ruby_11_多線程と包管理

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}"
           

以上代碼執行結果為:

Ruby_11_多線程と包管理

線程生命周期

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
           

運作效果如下:

Ruby_11_多線程と包管理

使用下列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 "主線程結束<---"
           

輸出結果如下:

Ruby_11_多線程と包管理

補充一下:

除了使用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_11_多線程と包管理

線程變量

線程可以有其私有變量,線程的私有變量線上程建立的時候寫入線程。

可以被線程範圍内使用,但是不能被線程外部進行共享。

但是有時候,線程的局部變量需要被别的線程或者主線程通路怎麼辦?

也不成問題,偉大的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}"
           

以上代碼運作輸出結果為:

Ruby_11_多線程と包管理

主線程等待子線程執行完成,然後分别輸出每個值。 。

線程優先級

線程的優先級 是影響線程的排程的主要因素。

其他因素包括 占用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']}"
}
           

運作效果如下:

Ruby_11_多線程と包管理

可以看到,把線程作為一個 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}"
           

以上執行個體運作輸出結果為:

Ruby_11_多線程と包管理

下面是使用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}"
           

以上執行個體運作輸出結果為:

Ruby_11_多線程と包管理

死鎖

兩個以上的運算單元,雙方都在等待對方停止運作,以擷取系統資源,但是沒有一方提前退出時,這種狀況,就稱為死鎖。

例如,一個程序 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)
           
Ruby_11_多線程と包管理

列出所有可用的遠端gem,例如:

gem list --remote
           
Ruby_11_多線程と包管理

為所有的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/
           
Ruby_11_多線程と包管理

接着,移除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'
...
           

未完待續,下一章節,つづく

繼續閱讀