多个线程调用相同的函数

Dus*_*ser 8 ruby stack multithreading

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

def foo 
  # do stuff ...
end

100.times do |i|
  Thread.new do
    foo
  end
end
Run Code Online (Sandbox Code Playgroud)

如果当前有两个或多个线程foo,那么它们各自共享相同的局部变量foo吗?

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

d11*_*wtq 6

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

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

澄清一下,在下面的示例中,局部变量由多个线程zip 共享,因为它是在当前作用域中定义的(线程不会更改作用域,它们只是在当前作用域中启动一个单独的并行执行线程)。

zip = 42

t = Thread.new do
  zip += 1
end

t.join

puts zip # => 43
Run Code Online (Sandbox Code Playgroud)

这里的连接拯救了我,但显然如果我将其保留在那里,那么线程中根本没有任何意义。如果我执行以下操作,将会很危险:

zip = 42

t = Thread.new do
  zip += 1
end

zip += 1

puts zip # => either 43 or 44, who knows?
Run Code Online (Sandbox Code Playgroud)

那是因为基本上有两个线程都试图zip同时修改。当您访问网络资源或增加数字等时,这一点变得很明显,如上所述。

然而,在下面的示例中,局部变量zip是在一个全新的作用域内创建的,因此两个线程实际上并没有同时写入同一个变量:

def foo
  zip = 42
  zip += 1 # => 43, in both threads
end

Thread.new do
  foo
end

foo
Run Code Online (Sandbox Code Playgroud)

有两个并行堆栈被管理,每个堆栈在foo方法内都有自己的局部变量。

然而,下面的代码是危险的:

@zip = 42 # somewhere else

def foo
  @zip += 1
end

Thread.new do
  foo
end

foo

puts @zip # => either 43 or 44, who knows?
Run Code Online (Sandbox Code Playgroud)

这是因为实例变量@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!
Run Code Online (Sandbox Code Playgroud)

如果当执行流到达该Mutex#synchronize行时,它会尝试锁定互斥体。如果成功,则进入该块并继续执行。一旦块完成,互斥体将再次解锁。如果互斥量已经被锁定,则线程会等待,直到它再次空闲......实际上,它就像一扇每次只有一个人可以穿过的门。

我希望这能解决问题。