两个关于Ruby线程的问题

8

首先问题一:

  • 如何创建一个不立即启动的线程?如果我使用没有块的initialize方法,会引发异常。

  • 如何子类化线程,以便添加一些自定义属性,但保持与基本线程类相同的功能?我也不想使用initialize(&block)方法。

为了更好地说明这一点:

对于第一个问题:

x = Thread.new
x.run = {
  # this should happen inside the thread
}
x.start # i want to manually start the thread

第二点:
x = MyThread.new
x.my_attribute = some_value
x.run = {
  # this should happen when the thread runs
}
x.start

我正在寻找类似于这个的东西。希望你能帮忙。

3
为什么你不能“只是”用x = Thread.new {etc}替换掉x.start - Brent.Longborough
3个回答

12

问题1

检查MRI 1.8.7源代码,没有明显的方法可以让线程处于“停止”状态。

您可以让线程在锁定的互斥量上阻塞,然后在想让线程继续运行时解锁互斥量。

#!/usr/bin/ruby1.8

go = Mutex.new
go.lock
t = Thread.new do
  puts "Thread waiting to go"
  go.lock
  puts "Thread going"
end
puts "Telling the thread to go"
go.unlock
puts "Waiting for the thread to complete"
t.join

# => Thread waiting to go
# => Telling the thread to go
# => Thread going
# => Waiting for the thread to complete

问题2(有点)

你知道吗,你可以向线程传递参数吗? 任何传递给Thread.new的东西都会作为块参数传递:

#!/usr/bin/ruby1.8

t = Thread.new(1, 2, 3) do |a, b, c|
  puts "Thread arguments: #{[a, b, c].inspect}"
  # => Thread arguments: [1, 2, 3]
end

还有"线程本地变量",即每个线程的键/值存储。使用Thread#[]=设置值,使用Thread#[]获取它们。您可以使用字符串或符号作为键。

#!/usr/bin/ruby1.8

go = Mutex.new
go.lock
t = Thread.new(1, 2, 3) do |a, b, c|
  go.lock
  p Thread.current[:foo]    # => "Foo!"
end  
t[:foo] = "Foo!"
go.unlock
t.join

问题2,真的吗

你可以按照自己的意愿去做。这是一项艰巨的工作,特别是当处理线程的通常方式如此简单时。你需要权衡利弊:

#!/usr/bin/ruby1.8

require 'forwardable'

class MyThread

  extend Forwardable

  def_delegator :@thread, :join
  def_delegator :@thread, :[]=
  def_delegator :@thread, :[]

  def initialize
    @go = Mutex.new
    @go.lock
    @thread = Thread.new do
      @go.lock
      @stufftodo.call
    end
  end

  def run(&block)
    @stufftodo = block
    @go.unlock
    @thread.join
  end

end

t = MyThread.new
t[:foo] = "Foo!"
t.run do
  puts Thread.current[:foo]
end
t.join

# => "Foo!"

1
感谢您抽出时间撰写这个详细的答案。我采用了线程变量,使用了Thread.current方法。您的回答非常有趣。谢谢! - Geo

3
stuff_to_do = lambda do 
   # this happens in the thread
end

x = Thread.new( &stuff_to_do )

&符号有什么作用?没有它,代码就会出错,但我想了解语法。 - Dan Jameson
@DanJameson,您可以使用“&”符号将proc作为块传递给函数,而不使用块语法。以下是一个示例,以下代码片段是等效的:foo { puts "This is the block syntax" }my_proc = lambda { puts "Passing a proc rather than using the block syntax" }; foo(&my_proc) - Hubro

2
忽略 Ruby-Doc 1.8.7的示例,因为它包含竞态条件。参见Ruby 2的示例或类似下面的内容:
我在 Ruby 2.0 和 Ruby 1.8.7 中测试过这个问题,发现在 1.8.7 中仅仅调用 #wakeup 是不够的,需要调用#run。以下代码似乎在两个版本中都可以工作:
t = Thread.new { Thread.stop; puts "HELLO" }
until t.stop?; end  # ensure thread has actually stopped before we tell it to resume
t.run

4
这里存在竞态条件。如果t.run在Thread.stop之前执行,那么线程将永远无法通过那个点。 - ChrisPhoenix
克里斯是正确的。我实际上遇到了这种情况。我已经更新了帖子,并加入了繁忙等待以避免竞争。 - Patrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接