18

Local variables defined outside of a thread seem to be visible from inside so that the following two uses of Thread.new seem to be the same:

a = :foo
Thread.new{puts a} # => :foo
Thread.new(a){|a| puts a} # => :foo

The document gives the example:

arr = []
a, b, c = 1, 2, 3
Thread.new(a,b,c){|d, e, f| arr << d << e << f}.join
arr #=> [1, 2, 3]

but since a, b, c are visible from inside of the created thread, this should also be the same as:

arr = []
a, b, c = 1, 2, 3
Thread.new{d, e, f = a, b, c; arr << d << e << f}.join
arr #=> [1, 2, 3]

Is there any difference? When do you need to pass local variables as arguments to Thread.new?

sawa
  • 165,429
  • 45
  • 277
  • 381

2 Answers2

25

When you pass a variable into a thread like that, then the thread makes a local copy of the variable and uses it, so modifications to it do not affect the variable outside of the thread you passed in

a = "foo"
Thread.new{ a = "new"}
p a # => "new"
Thread.new(a){|d| d = "old"} 
p a # => "new"
p d # => undefined
sawa
  • 165,429
  • 45
  • 277
  • 381
concept47
  • 30,257
  • 12
  • 52
  • 74
  • 1
    If we are are overriding value of `d` inside the thread then what is the use-case to pass it as an argument? And if we are performing any operation on `d` then it is reflecting in the value of `a`. ``` a = "foo" Thread.new { a = "new" } p a Thread.new(a) do |d| d.prepend("old") puts d # => "oldnew" puts a # => "oldnew" end ``` – Kuldeep Jul 07 '14 at 05:24
  • When you say "local copy", do you mean that a shallow copy is created with `#dup` or `#clone`? Or a deep copy? I would guess a shallow copy with either. So it attempts to help with good thread discipline, but only goes so-far. It would have been nice if the ruby Thread class documentation was a bit more specific on this subject. Thanks for the clear example and answer. – pjvleeuwen Feb 29 '20 at 19:05
  • A quick tests shows that in your example `a` and `d` would still have the same `object_id`. So only the variable is new, but refers to the same (non-dupped/-cloned) object. So doing `d[1] = 'o'` would result in `a = 'now'`. `a = 'new'; Thread.new(a) { |d| d[1] = 'o' }.join; puts a` – pjvleeuwen Feb 29 '20 at 19:15
1

I think I hit the actual problem. With a code like this:

    sock = Socket.unix_server_socket(SOCK)
    sock.listen 10
    while conn = sock.accept do
        io, address = conn
        STDERR.puts "#{io.fileno}: Accepted connection from '#{address}'"
        Thread.new{ serve io }
    end

it appears to work when accepting few connections. The problem comes when accepting connections quickly one after another. The update to local variable io will be reflected in multiple concurrent threads unless passed as argument to Thread.new