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.
Ruby core classes aren't thread safe
21–30 of 47 posts
Re: Ruby core classes aren't thread safe
#22Earlier 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.
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
#23It'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…
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
#24Under 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…
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
#25This 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).
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
#26It'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…
Re: Ruby core classes aren't thread safe
#27Earlier 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.
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 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
#29Does 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.
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
#30Earlier 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".
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.