10

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

def foo 
  # do stuff ...
end

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

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

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

4

2 回答 2

5

是的,它们共享相同的变量。这是线程的关键元素,在只读上下文中很好,但是如果它们写入任何这些变量,则需要使用 aMutexsynchronize线程,因此在任何给定时间只有一个可以更改变量。有时他们可能会调用间接更改数据的方法,因此在决定是否需要同步之前,您需要完全了解系统。

至于你的第二个问题,如果我理解你的问题,它们有单独的堆栈帧,它们仍然在内存中共享相同的数据。

澄清一下,在以下示例中,局部变量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行时,它会尝试锁定互斥锁。如果成功,则进入块并继续执行。块完成后,互斥锁将再次解锁。如果互斥锁已经被锁定,线程会一直等待,直到它再次空闲……实际上它就像一扇门,一次只有一个人可以穿过。

我希望这能解决问题。

于 2012-05-06T02:23:58.770 回答
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 中的值。

于 2012-05-06T08:18:38.383 回答