Ruby_11_多线程と包管理

本文详细介绍了Ruby中的多线程编程,包括线程创建、生命周期、同步控制等内容,并提供了丰富的示例代码。此外,还讲解了如何处理线程中的异常、实现线程间的通信以及避免死锁等问题。


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种状态:

线程状态返回值
Runnablerun
SleepingSleeping
Abortingaborting
Terminated normallyfalse
Terminated with exceptionnil

线程和异常

当某线程发生异常,且没有被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(线程) 类方法如下:

序号方法描述
1Thread.abort_on_exception
若其值为真的话,一旦某线程因异常而终止时,整个解释器就会被中断。它的默认值是假,也就是说,在通常情况下,若某线程发生异常且该异常未被Thread#join等检测到时,该线程会被无警告地终止。
2Thread.abort_on_exception=
如果设置为 true, 一旦某线程因异常而终止时,整个解释器就会被中断。返回新的状态
3Thread.critical
返回布尔值。
4Thread.critical=
当其值为true时,将不会进行线程切换。若当前线程挂起(stop)或有信号(signal)干预时,其值将自动变为false。
5Thread.current
返回当前运行中的线程(当前线程)。
6Thread.exit
终止当前线程的运行。返回当前线程。若当前线程是唯一的一个线程时,将使用exit(0)来终止它的运行。
7Thread.fork { block }
与 Thread.new 一样生成线程。
8Thread.kill( aThread )
终止线程的运行.
9Thread.list
返回处于运行状态或挂起状态的活线程的数组。
10Thread.main
返回主线程。
11Thread.new( [ arg ]* ) {| args | block }
生成线程,并开始执行。数会被原封不动地传递给块. 这就可以在启动线程的同时,将值传递给该线程所固有的局部变量。
12Thread.pass
将运行权交给其他线程. 它不会改变运行中的线程的状态,而是将控制权交给其他可运行的线程(显式的线程调度)。
13Thread.start( [ args ]* ) {| args | block }
生成线程,并开始执行。数会被原封不动地传递给块. 这就可以在启动线程的同时,将值传递给该线程所固有的局部变量。
14Thread.stop
将当前线程挂起,直到其他线程使用run方法再次唤醒该线程。

线程实例化方法

以下实例调用了线程实例化方法 join:

#!/usr/bin/ruby

thr = Thread.new do   # 实例化
   puts "In second thread"
   raise "Raise exception"
end
thr.join   # 调用实例化方法 join

以下是完整实例化方法列表:

序号方法描述
1thr[ name ]
取出线程内与name相对应的固有数据。 name可以是字符串或符号。 若没有与name相对应的数据时, 返回nil。
2thr[ name ] =
设置线程内name相对应的固有数据的值, name可以是字符串或符号。 若设为nil时, 将删除该线程内对应数据。
3thr.abort_on_exception
返回布尔值。
4thr.abort_on_exception=
若其值为true的话,一旦某线程因异常而终止时,整个解释器就会被中断。
5thr.alive?
若线程是"活"的,就返回true。
6thr.exit
终止线程的运行。返回self。
7thr.join
挂起当前线程,直到self线程终止运行为止. 若self因异常而终止时, 将会当前线程引发同样的异常。
8thr.key?
若与name相对应的线程固有数据已经被定义的话,就返回true
9thr.kill
类似于 Thread.exit 。
10thr.priority
返回线程的优先度. 优先度的默认值为0. 该值越大则优先度越高.
11thr.priority=
设定线程的优先度. 也可以将其设定为负数.
12thr.raise( anException )
在该线程内强行引发异常.
13thr.run
重新启动被挂起(stop)的线程. 与wakeup不同的是,它将立即进行线程的切换. 若对死进程使用该方法时, 将引发ThreadError异常.
14thr.safe_level
返回self 的安全等级. 当前线程的safe_level与$SAFE相同.
15thr.status
使用字符串"run"、"sleep"或"aborting" 来表示活线程的状态. 若某线程是正常终止的话,就返回false. 若因异常而终止的话,就返回nil。
16thr.stop?
若线程处于终止状态(dead)或被挂起(stop)时,返回true.
17thr.value
一直等到self线程终止运行(等同于join)后,返回该线程的块的返回值. 若在线程的运行过程中发生了异常, 就会再次引发该异常.
18thr.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 版本,也可以通过手动安装:

更新 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:

gem list --local
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

 

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


如果你使用 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'
...


未完待续,下一章节,つづく


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值