多个线程调用同一个函数

10

假设我们有多个线程都调用同一个函数:

def foo 
  # do stuff ...
end

100.times do |i|
  Thread.new do
    foo
  end
end

如果两个或更多线程当前正在 foo 内部,它们是否共享 foo 内的相同局部变量?

这与我的第二个问题有关。线程是否具有单独的堆栈帧,或者它们在单个进程中共享堆栈帧?具体而言,当多个线程每个调用 foo 并在 foo 返回之前,堆栈上会有多个 foo 副本,每个副本都有自己的局部变量,还是堆栈上只有一个 foo 副本?

2个回答

6
是的,它们共享同样的变量。这是线程的一个关键元素,在只读环境下没有问题,但如果它们写入任何这些变量,则需要使用互斥锁并使线程同步,以便在任何给定时间仅有一个线程可以更改变量。有时,它们可能会调用间接更改数据的方法,因此在决定是否需要同步之前,您需要完全了解系统。
至于您的第二个问题,如果我理解您的问题,它们具有单独的堆栈帧,但是它们仍然共享内存中的相同数据。
为澄清起见,在以下示例中,本地变量zip由多个线程共享,因为它在当前范围内定义(线程不会更改作用域,它们只是在当前范围内启动一个单独的并行执行线程)。
zip = 42

t = Thread.new do
  zip += 1
end

t.join

puts zip # => 43

这里的连接救了我,但如果我保留它,显然线程就没有任何意义。如果我继续这样做,会很危险:

zip = 42

t = Thread.new do
  zip += 1
end

zip += 1

puts zip # => either 43 or 44, who knows?

那是因为你基本上有两个线程同时尝试修改zip。当你访问网络资源或递增数字等时,这就变得明显起来了。在下面的例子中,局部变量zip在一个全新的作用域内创建,所以两个线程实际上不是同时写入同一个变量:
def foo
  zip = 42
  zip += 1 # => 43, in both threads
end

Thread.new do
  foo
end

foo

有两个平行的堆栈被管理,每个堆栈都有自己的本地变量在foo方法内。

但是下面的代码很危险:

@zip = 42 # somewhere else

def foo
  @zip += 1
end

Thread.new do
  foo
end

foo

puts @zip # => either 43 or 44, who knows?

这是因为实例变量@zip可以在foo函数的范围之外访问,因此两个线程可能同时访问它。

这些“两个线程同时更改相同数据”的问题可通过在更改变量的代码段周围谨慎地放置互斥锁(锁)来解决。必须在创建线程之前创建互斥锁,因为在互斥锁的情况下,非常重要的一点是两个线程访问相同的互斥锁,以便知道它是否被锁定。

# somewhere else...
@mutex = Mutex.new
@zip   = 42

def foo
  @mutex.synchronize do
    @foo += 1
  end
end

Thread.new do
  foo
end

foo

puts @zip # => 44, for sure!

当执行流程到达 Mutex#synchronize 代码行时,会尝试锁定互斥锁。如果成功,它将进入块并继续执行。一旦块完成,互斥锁将再次解锁。如果互斥锁已经被锁定,线程将等待直到它再次变成自由状态......实际上这类似于一个只能同时通过一个人的门。

我希望这样能够解决您的问题。


你能举一个只读上下文的例子吗?如果foo创建了一个本地变量,它不是必须要给它赋值吗? - Dustin Biser
我在这里阅读的内容:http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_threads.html 中的“线程变量”主题听起来不同。或者我有什么遗漏了吗? - alk
1
啊,我误解了。如果foo函数创建一个本地变量,那就没问题了。但是,如果它创建了一个实例变量,其他线程可以访问它,那么它应该使用Mutex。当我说“只读”时,我只是指不修改实例变量/全局变量。本地变量没问题...它们属于当前线程。 - d11wtq
1
这个主题所说的关于“局部变量”的是,如果一个局部变量在线程启动之前被创建,并且仍然在该线程内部范围内,则该局部变量将被多个线程访问。但是当您调用方法时,会创建一个全新的作用域,因此没有问题。 - d11wtq
我理解的是我们应该使用@zip而不是@foo,对吗? - skwisgaar

0

在方法内定义的局部变量不是共享的。但是,如果线程块的范围内存在相同对象的实例变量,则线程可以访问它。

例如:

def foobar
    puts "Foo is defined!" if defined?(foo)=='local-variable'
    foo = 5
end

如果被多个线程调用,就不会放置字符串。

但是以下内容需要使用互斥锁进行同步,因为存在竞态条件:

foo = {bar:5}
def foobar(value)
    value[:bar]+=5
end
15.times{|i| Thread.new{foobar foo}}

接下来,foo[:bar] 可能包含一个值为35,因为每次调用 foobar 都会改变哈希表 foo 中的一个值。


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