Live data from Hacker News

Ruby core classes aren't thread safe

jstorimer.com

21–30 of 47 posts

Re: Ruby core classes aren't thread safe

#21
post #3

Does the spec mandate thread safety? (I guess I should ask if there's a spec or is MRI the reference implementation)

AFAIK there is no spec. MRI is the reference implementation, but many things are experimental or intentionally unspecified. Given that MRI ships with a GIL, the only core classes that are intentionally aware of multi-threading concerns are Mutex, ConditionVariable, and Queue.

Even a thread-aware collection where collection methods are synchronized on an internal lock (as in Java 1.0 — this was quickly dropped as it's effectively useless) wouldn't help here: having `[]` and `[]=` safe will not make calling `[]`, performing an addition and then calling `[]=` safe.

Re: Ruby core classes aren't thread safe

#22
post #12

Earlier quoted context omitted.

> and probably Haskell somehow By design - pure values are always thread safe.

Pure values are not really relevant: you still need to update a binding somewhere to synchronize, and that's sufficient for your race. The clojure example wouldn't be safe if atoms weren't compare-and-set: have collection state A, thread one applies A->B, thread two applies A->C, the two threads set the atom (atomically but not CAS) and an increment has been lost even though all values are pure.

Yes, but the point is, that in a functional language, hash-tables need not be thread-safe, as they are immutable. Only the variable binding has to be transactional.

In a functional language, the code would be:

  transaction {
    local a = !x
    local b = copy a with b[i] = a[i] + 1
    x := b
  }
where `!x` is referencing a transactional variable and `:=` is setting it.

Re: Ruby core classes aren't thread safe

#23
post #18

It's not that Arrays are not thread safe; it's just that the code was written in a non-thread-safe way. Writing x[i] -= 1 actually means x[i] = x[i] - 1 So, there's a read, a subtraction, and a write, and they all happen sequentially. Since they are not in a transaction or protected by a mutex, nothing guarantees that other thread don't mutate `x[i]` in the mean time. This has nothing to do with Ruby, and nothing to…

I came to this comment thread specifically to point this out, but you beat me to it.

It has nothing to do thread safety, and everything to do with atomicity. This is not a single atomic operation, but rather three atomic (and thread-safe) operations which are bound together with the assumption that the entire thing is atomic when it is not.

    # you might as well imagine this happening
    original = x[i]
    new_val = original - 1
    x[i] = new_val
Which is why you need to use a mutex to make the entire operation atomic, at a small performance cost.

The Objective-C compiler actually adds a nice bit of syntax for this, you can simply wrap your code in @synchronized:

    @synchronized(self) {
         int original = x[i];
         int new_val = original - 1;
         x[i] = new_val;
    }

Re: Ruby core classes aren't thread safe

#24
post #20
post #7

Under the semantics described here, Java or C# core classes aren't "thread-safe" either and I'd expect the vast majority of standard libraries to completely fail the test (potential winners: Clojure using an immutable collection bound on an atom, as they have compare-and-swap semantics; and probably Haskell somehow), the example code requires performing the following actions atomically: * Loading an instance-local co…

> Under the semantics described here, Java or C# core classes aren't "thread-safe" either and I'd expect the vast majority of standard libraries to completely fail the test (potential winners: Clojure using an immutable collection bound on an atom, as they have compare-and-swap semantics; and probably Haskell somehow) Java's standard library includes Doug Lea's famous java.util.concurrent package written as part of J…

> Java's standard library includes Doug Lea's famous java.util.concurrent package written as part of JSR 166

Irrelevant, java.util.concurrent.atomic.AtomicIntegerArray - which I mentioned — can not under any sensible definition be called a "core collection".

Re: Ruby core classes aren't thread safe

#25
post #9
post #5

This still doesn't explain why the MRI implementation is accidentally threadsafe. Why doesn't the interpreter switch threads after reading the value from the hash but before storing the updated value?

Due to the Global Interpreter Lock (GIL), your whole script is wrapped in one giant mutex. That means that you don't have code running in true parallel, so the data is not corrupted as it is in JRuby and Rubinius (which both implement real, true, parallel threads).

That doesn't explain it. The runtime execution could still be like this:

  thread #1:
    local a = x[i]
    a = a + 1
    context_switch()

  thread #2:
    local b = x[i]
    b = b + 1
    x[i] = b
    context_switch()

  thread #1:
    x[i] = a
resulting in a wrong (or merely unexpected) result.

Re: Ruby core classes aren't thread safe

#26
post #18

It's not that Arrays are not thread safe; it's just that the code was written in a non-thread-safe way. Writing x[i] -= 1 actually means x[i] = x[i] - 1 So, there's a read, a subtraction, and a write, and they all happen sequentially. Since they are not in a transaction or protected by a mutex, nothing guarantees that other thread don't mutate `x[i]` in the mean time. This has nothing to do with Ruby, and nothing to…

That's not quite true. The language spec can mandate that the -= be atomic (the X86 equivalent is mandating a LOCK).

Re: Ruby core classes aren't thread safe

#27
post #22

Earlier quoted context omitted.

Pure values are not really relevant: you still need to update a binding somewhere to synchronize, and that's sufficient for your race. The clojure example wouldn't be safe if atoms weren't compare-and-set: have collection state A, thread one applies A->B, thread two applies A->C, the two threads set the atom (atomically but not CAS) and an increment has been lost even though all values are pure.

Yes, but the point is, that in a functional language, hash-tables need not be thread-safe, as they are immutable. Only the variable binding has to be transactional. In a functional language, the code would be: transaction { local a = !x local b = copy a with b[i] = a[i] + 1 x := b } where `!x` is referencing a transactional variable and `:=` is setting it.

> Yes, but the point is, that in a functional language, hash-tables need not be thread-safe

As I and you noted, that's irrelevant, making the collection "thread-safe" (in the usual acception of the term, namely that concurrent accesses to the collection will not put the collection in an incorrect state) would not fix the situation since the increment is done outside the collection.

> Only the variable binding has to be transactional.

My point being precisely that the binding still has to be transactional: pure values are not sufficient to save you.

> In a functional language, the code would be:

Erm... I know. And that's not "in a functional language" that's in haskell, other languages will use different solutions.

Re: Ruby core classes aren't thread safe

#28
The article seems to be wrong in several aspects ... First, the issue described has nothing to do with arrays; the same problem happens when using a plain number:

    class Inventory
      def initialize(nb)
        @nb_items = nb
      end
     
      def decrease
        @nb_items -= 1
      end
     
      def nb_items
        @nb_items
      end
    end 

    @inventory = Inventory.new(4000) 


    threads = Array.new
    400.times do
      threads 
Second, the mutex in the OP's code synchronizes the whole block passed to a thread, i.e. there's no parallelism at all (the second thread waits until the first one finishes, and so on). It should rather be something like:

    class Inventory
      def initialize(nb)
        @nb_items = nb

        @lock = Mutex.new
      end
     
      def decrease
        @lock.synchronize do
          @nb_items -= 1
        end
      end
     
      def nb_items
        @nb_items
      end
    end 

    @inventory = Inventory.new(4000) 

    threads = Array.new

    400.times do
      threads 

Re: Ruby core classes aren't thread safe

#29
post #3

Does the spec mandate thread safety? (I guess I should ask if there's a spec or is MRI the reference implementation)

AFAIK there is no spec. MRI is the reference implementation, but many things are experimental or intentionally unspecified. Given that MRI ships with a GIL, the only core classes that are intentionally aware of multi-threading concerns are Mutex, ConditionVariable, and Queue.

A GIL does not mean classes should ignore concurrency concerns, it's still possible to get odd behaviour from things like hash table implementations in a GIL based interpreter when inserting objects as you may end up thread switching mid operation.

What saves you most of the time is that it isn't worth switching threads too often so normally you get lucky.

Re: Ruby core classes aren't thread safe

#30
post #20

Earlier quoted context omitted.

> Under the semantics described here, Java or C# core classes aren't "thread-safe" either and I'd expect the vast majority of standard libraries to completely fail the test (potential winners: Clojure using an immutable collection bound on an atom, as they have compare-and-swap semantics; and probably Haskell somehow) Java's standard library includes Doug Lea's famous java.util.concurrent package written as part of J…

> Java's standard library includes Doug Lea's famous java.util.concurrent package written as part of JSR 166 Irrelevant, java.util.concurrent.atomic.AtomicIntegerArray - which I mentioned — can not under any sensible definition be called a "core collection".

The "core collections" - a term you're defining yourself right now however you like, for the record - aren't threadsafe for a reason. They have different performance characteristics in Java! You need two versions of the collections in these languages because of their concurrency and memory models.

In Java, using Concurrent/Atomic classes is how you write idiomatic threadsafe code, and it has been for a decade. Bitching because the main list types aren't threadsafe only demonstrates deep, deep ignorance.

Post reply on HN