http://stackoverflow.com/questions/15184338/how-to-know-what...
http://stackoverflow.com/questions/15184338/how-to-know-what...
Yeah, Ruby core data structures are not thread-safe. But that doesn't matter. You're not supposed to a lot of any data structures between requests anyway. Of the few that are shared (e.g. the Rails cache object), they are explicitly engineered to be thread-safe.
There's also the story that Ruby cannot use multi-core. That's partially correct: on the MRI implementation, it doesn't matter how many threads you have, only one can be active at a time. However what you can have is multiple processes, each with multiple threads. Each process can use a different core. Furthermore, the JRuby and Rubinius implementations can handle multi-core with multithreading just fine. Both Phusion Passenger and Puma support JRuby and Rubinius.
Maybe I'm just too familiar with Ruby and multithreading, but I don't understand why people have trouble with multithreaded Ruby. In my eyes it's pretty easy. If you have a library that's not thread-safe, then just don't share that library's objects between threads, or ensure that you grab a lock before using that object, and you'll be fine. In my opinion, the situation is not much different in Python, Java or C++.
I think the problem is a mix of language features and culture. Ruby does not have a history good thread-safe practices, and the language has some extremely convenient features which are death for thread-safety (eg. class instance variables). Combine that with prolific meta-programming and a lack of static analysis tools, and it can become very very difficult to be certain that a given application is thread safe. As long as all developers are well-versed and keep thread-safety front and center from the beginning of application developer then I agree it's not hard per se, but in practice that is so rarely the case that if I knew I needed heavy multi-threading for memory efficiency and CPU utilization I might disqualify Ruby on cultural reasons alone (and I say this as a full-time rubyist who knows no language better).
A web application doesn't usually share much in-memory state _between requests_ -- especially if we're talking about app-specific code and not framework code. If it does (modify class variables or other global state, etc) -- you've got to eliminate that or make it threadsafe, but that's not _that_ hard to identify. A web application is very rarely going to be doing class-modifying meta-programming _as part of request handling_ (as opposed to on boot, where it won't be a problem).
Your actual app-specific code in a web application is highly likely to be MT-request-dispatch safe already, and if not is not too hard to get it there -- and worth the effort because of the extreme throughput increases you can get with an MT-request-dispatch app server.
Now, what can definitely be trickier is framework and gem code. ActiveRecord, historically, has had quite a few problems with thread-safety under MT-request-dispatch, off and on (the database connections themselves are the shared state it's tricky to deal with, among other things). But it's gotten a LOT better and should be fairly robust now.
I know what you mean about 'cultural reasons', but I think the ruby culture has been gradually changing for a while (jruby has a lot to do with it), and some of us hope is becoming more MT-friendly.
But I'm not saying it's trivial or guaranteed problem free, but the potential gains are worth it.
(I do run a rails app that uses multiple threads and ActiveRecord.)
Weeell, but this (class-level state) is true of virtually all non-functional environments though, right? -- including the otherwise excellent concurrency support within the JVM.
If someone's using class/global state in a contentious, multi-threaded environment, they...probably should not be developing on that particular problem.
As you pointed out, the fact of class-level state isn't so much an argument against Ruby per se. To me it's more of an argument in favor of stateless/functional programming where data contention is high -- which is somewhat besides the point in this conversation because we're talking about (mostly) "embarrassingly parallel" concerns where data tends to be siloed and contention is low.
>> if I knew I needed heavy multi-threading for memory efficiency and CPU utilization I might disqualify Ruby on cultural reasons alone
But...aren't these exactly the kind of problems that REE/Passenger, Unicorn and Puma (not to mention EventMachine and Rack) have been digging into for years now?
As much of a bad rap as Ruby tends to get when it comes to threading, the GIL and so forth, I think what gets lost in that conversation is the fact that many flavors of Ruby application server configurations are serving thousands and thousands of concurrent requests in production every second and doing a pretty good job of it. In certain contexts, Ruby handles concurrency and parallelism admirably well.
"Culture" is a relative question -- are we talking about the guys at Phusion, or Engine Yard, or Heroku, or someone like Evan Phoenix, or Yehuda? Is that Ruby culture? There's some heavy threading brain-power in that group, and they've left a pretty big footprint on the Ruby/Rails community.
Or are we talking about (no offense) journeyman web coders who generally don't have to solve threading/high-contention, CPU-intensive problems all the time? I'm honestly not sure that at this point the Ruby community is any worse off than other communities in that regard.
In 2013, the Ruby/Threading issue is starting to feel (to me) a bit like the "Java is slow" notion did in the early 2000s. Might be time to look at what Ruby is actually doing in production and rethink that idea.
For applications that utilize more blocking I/O though, e.g. applications that perform a lot of HTTP calls, you need a multithreaded or an evented app server, as well as an app written to support that concurrency style. Otherwise you will run out of concurrency very soon, and your app will spend a lot of time waiting on I/O instead of doing useful work.
If the request-response loop spends a non-trivial amount of time waiting on I/O -- and most do -- then concurrent request dispatch has huge advantages. I tried to investigate/show that in these benchmarks: https://github.com/jrochkind/fake_work_app/blob/master/READM...
(And writing a ruby app to actually work right with evented/reactor-style request dispatch turns out to be way worse than multi-threading, in actual practice. http://www.slideshare.net/KyleDrake/hybrid-concurrency-patte... )
(Your reference to 'blocking I/O' is confusing, I think -- that phrase means different and sometimes opposite things to different people/contexts. But it's not even neccesary to get into it for this discussion, you just mean 'I/O' at all, I think.)
With blocking I/O I mean I/O calls which put the process to sleep for a while. This is in contrast to I/O calls which succeed immediately.
In other contexts, people talk about "non-blocking IO" and "blocking IO" to mean, well, 'blocking IO' is where the process or thread is _not_ put to sleep, it's not being switched out _even though_ it's waiting on IO. For instance, most ruby database drivers used to be broken in this way with respect to multi-threading, but were fixed to be "non-blocking IO", so threads would actually be unscheduled (even in MRI) when waiting on IO.
For examples of this use of "blocking IO" vs "non-blocking IO" see http://en.wikipedia.org/wiki/Asynchronous_I/O (Ha, look what wikipedia URL did to "I/O").
That usage is pretty common, so I think your different use of 'blocking IO' is confusing.