Ruby 线程 join

liker · 2015年06月01日 · 最后由 xu_xiang_yang 回复于 2015年06月06日 · 4405 次阅读
require '******'  #====drb client 生成的gem
p Thread.list  
a = ARGV.first
ts = []
a.to_i.times do |i|
  ts << Thread.new(i) do
    Thread.stop
    sleep(i+1)
    p i+1
  end
end
ts.each{|t| t.run}
ts.each &:join
=begin
ruby a.rb 10
[#<Thread:0x000000020ee640 run>, #<Thread:0x00000005035188 sleep>]
1
2
4
5
6
7
8
9
10
=end

该实例结果一直处于等待某一线程,how to deal with it

ts.map &:join 试试

#1 楼 @MrPasserby map 和 each 在这应该没区别吧 测试结果大了还是一样

#2 楼 @liker join 不是把线程放到 current thread 下执行么,不知道你要的是什么效果,1~10 乱序排列的效果吗?

#3 楼 @MrPasserby 效果就是 主线程等待 ts 中所有线程执行完毕之后关闭,ts 中的线程有的不能执行所以 一直等待

Sleep 遇到 GIL 了,所以你的打印是顺序的

#5 楼 @huacnlee sleep 只会阻塞当前线程,不会阻塞其他线程,下面代码大约在 3 秒内能跑完

100.times.map{|i| Thread.new{ sleep 3; puts i} }.each &:join

#6 楼 @luikore 额,那我记错了

不明白,不明白,既然要等待子线程跑完,就不用先 stop 再 run 了

p Thread.list  
a = ARGV.first
ts = []
a.to_i.times do |i|
  ts << Thread.new(i) do
    #Thread.stop
    sleep(i+1)
    p i+1
  end
end
# 加上这个就会等很久很久才退出
# ts << Thread.new { sleep 10**100 }
#ts.each{|t| t.run}
ts.each(&:join)
# map 可以把所有进程的状态输出, happy!
# puts ts.map(&:join)  

线程里面先 stop 再 run 是不可行的,因为你的线程可能还没来得及 stop 就已经先执行 run 的命令了。只是因为你线程第一次执行的命令是 Thead.stop,所以这个可能性比较小,但是多试几次还是会出现这种情况的。如果线程一直保持 stop,你再去 join 它就会出现 deadlock

[#<Thread:0x000000006c2668 run>]
1
3
4
5
6
7
8
9
10
test.rb:12:in `join': No live threads left. Deadlock? (fatal)
    from test.rb:12:in `block in <main>'
    from test.rb:12:in `map'
    from test.rb:12:in `<main>'

PS: 其实我还是不懂你要啥效果=_=

以下都是执行 10 次的结果 ruby a_test.rb 10

#encoding: utf-8
require '*******'  #====drb client 生成的gem
require 'benchmark'
real_time = Benchmark.realtime do
  a = ARGV.first
  ts = []
  t = []
  a.to_i.times do |i|
    ts << Thread.new(i) do
      ti = Benchmark.realtime do
        sleep(1)
      end
      t << ti
    end.join
  end
  p t
  sum = t.inject{|s,e| s=s+e}
  p sum/t.size
end
p real_time

运行结果为

[1.000672781, 1.000814113, 1.000668882, 1.000586453, 1.000551037, 1.000492891, 1.000694687, 1.000767984, 1.000800398, 1.000598385]
1.0006647611000001
10.01287766 #==>总共耗时10s

所以加了 stop 和 run 操作;but 修改之后如:

#encoding: utf-8
require '*******'
require 'benchmark'
real_time = Benchmark.realtime do
  a = ARGV.first
  ts = []
  t = []
  a.to_i.times do |i|
    ts << Thread.new(i) do
      Thread.stop
      ti = Benchmark.realtime do
        sleep(1)
      end
      t << ti
    end
  end
  ts.each &:run
  ts.each &:join  #有时主线程会一直等待第一个 ts[0]线程的运行 ts[0]没有运行结果所以修改为ts[4..-1].each &:join
  p t
  sum = t.inject{|s,e| s=s+e}
  p sum/t.size
end
p real_time

修改 ts[4..-1].each &:join 之后也会出现等待状态 但是执行成功的几率比较大。结果如下:

[1.001274903, 1.000687374, 1.000907523, 1.001395518, 1.000877703, 1.001985597, 1.002118701, 1.00204419, 1.002451825] ##基本上size为8或者9
1.001527037111111
1.021161041 ##总耗时比较短 所以用了stop操作

我只用于测试一些方法的具体时间(模拟高并发情况下);ts join 会出现等待主线程一直等待状态,请大神解惑

#9 楼 @MrPasserby 恩 谢啦 知道了 在 run 之前先确保已经 stop 了 所以

sleep 0.1
ts.each &:run
ts.each &:join

THX

做个广告。。。。自己做的 gem,https://github.com/xuxiangyang/go_chanel 用来封装线程执行的细节。只需要关心每一步的并发量就好了

#13 楼 @shiningray 本质上没什么区别,因为我写的时候是因为我们的系统需要一个这种东西。我又没有找到,就自己写了一个。现在就在线上系统中用了。

区别应该是不用控制并发。 我对 golang 中的 channel 和 go 进行了封装,你不用考虑线程间的同步,各种乱七八糟的问题,只需要把你自己的业务拆分成几步,每一步定好他的并发量,然后上一步的返回值是下一步的输入值,就 ok 了。

并且我没有引入其他的新概念,比如 select 什么的。这样学习成本更低一些。 在我看来,如果是用 ruby,那么对于并发量的要求不会那么高,如果并发量很大,我觉得应该用 golang。

#13 楼 @shiningray pipe 封装 这个是封装,很接近 linux 中的通道的感觉,并且是看不出并发的代码的

需要 登录 后方可回复, 如果你还没有账号请 注册新账号