Why threads vs events is a nonsensical question.
swtch.com
swtch.com
That was a great explanation, though.
To be fair to Russ, it is his slidedeck from doing his talk, not a publication. It was only the 2nd IWP9, we've got a bit better since then :)
(and there is a hint that there is more to it, in that it showns the slide # in the bottom right corner)
log(write(process(read(source))))
becomes: read(source, function(input) {
write(process(input), function(success) {
log(success)
})
})And of course thread based code can do other things as well during blocking IO - on other threads.
do {
source = <- sources // read a source from the sources channel
if source
data = read(source)
process <- data // send it to the process channel
else
process <- nil
} while source
fn processor {
do {
data = <- process
result = process(data)
results <- result
} while data
}
fn writer {
do {
data = <- results
msg = write(data)
log <- msg
} while data
}
fn logger {
do {
msg = <- log
if msg
print msg
} while msg
} parallel for(item in source)
log(write(process(read(item))))
And let the compiler/runtime figure out how to do that. It could generate something similar to your code for instance.channels rule :)
We think of "threads" as one of the options. The thing is that we've mostly been using threads with locks (e.g. Java), and slide #3 points out where we have gone wrong:
Drawbacks of threads
...
Drawbacks of events
...
Actually drawbacks of locks and top-level select loops!
In fact, it is "locks" that humans have trouble with.Note the title of the presentation - Threads without Locks - suggesting another option. (as others have noted, this presentation describes Go, and I personally believe this is why Go will do well)
edit: wording, formatting
Currently we still have shared mutable state that sometimes requires locking (unless you resort to a functional style of programming): http://hns.github.com/2011/05/12/threads.html
This being said, event-driven as a design choice has much to recommend it.
while( connection = accept_connection ){
in_thread_run(handle_connection, connection)
}sometimes you just can't get enough from slides...
Selection of sane data structures and communication channels can get you virtually all of the safety and ease of separate processes WITH the performance benefits of a shared memory space.
It reminds me of people that criticize C++ for allowing memory leaks. There as here, simply selecting the right primitives and development strategy in advance make the problem simply disappear.
Nevermind the fact that (almost) all the things that (supposedly) make threads evil are still there when you use heavyweight processes.
You can't share memory unknowingly with processes, when you can share memory at all.
Besides that, you still have to deal with synchronization and mutual exclusion. Or you use a message passing model, which you can do with threads too, though your points in your other comment apply there, of course.
Is it safe? Does it say it's safe? Do you believe it? What about the next release? If you single thread all calls to the library to be safe, is your program still provably deadlock free?
The unknowns eat up a lot of thinking.
What about the other 5%? Working around the lack of threads in those remaining instances takes orders of magnitude more work. Anyone who has ever implemented complex data structures in shared memory or memory mapped files knows that. No pointers, no new/delete/malloc/free, no garbage collector, just a big blob.
It's definitely more difficult than using only a few well documented, high quality, libraries in the parts of the code where it matters.
If you are writing a quick script, then you don't want to be picking through your libraries source code for thread safety.
But 99% of people who use threads are just looking to keep a GUI looking responsive, which only takes a couple of processes anyway. Sand-boxing different components into their own process seems to be the way players like Google (Chrome) and Apple (Lion) are going anyway.
While threads have their place, I think it's the same kind of place that inlined assembly should be considered.
Anyway, I'm mostly a server-side engineer, so I can't speak well to the disastrous mess of GUI libraries and such, but the libraries I use are, indeed, thread-safe, and I'm quite confident that they will remain so. Their state is maintained through handles, not global variables.
Also server side engineers aren't necessarily safe; there are a number of C library functions that are not particularly reentrant. This has been known for decades because reentrancy is important in signal handling situations. The safest way of dealing with asynchronous signals, in fact, is to use global flag variables and use the flag as an indication that one should e.g. call waitpid because at least one SIGCHLD has come in since you last went through the main loop. You may deny that this happens particularly often, but that particular hack was an important simplification of my life a few months ago.
Insisting processes be used != insisting OS processes be used. Although most language don't give any choice in the matter.
> Selection of sane data structures and communication channels can get you virtually all of the safety and ease of separate processes
It gives you none of the safety, as you have to be very careful in ensuring no mutable datastructure is ever shared unknowingly. When using processes, you can't share memory implicitly, which is safe.
> There as here, simply selecting the right primitives and development strategy in advance make the problem simply disappear.
That's bullshit. It may make the problem less prominent, but it can not make the problem disappear.
A facsimile of a process that isn't implemented as an actual process is going to be in a shared address space. Your pet bondage-and-discipline language might work to prevent one pseudo-process from interfering with another, but I don't see it being equal to full-blown processes, nor do I see it being substantively more trustworthy than making a few simple, easy decisions about how to structure your programs.
That's kind-of the whole point, and difference between threads and processes. If you have implicitly shared mutable memory with processes, your processes are broken and you have threads.
> I don't see it being equal to full-blown processes
Really?
> nor do I see it being substantively more trustworthy
That's interesting. So you don't see how the language enforcing a share-less discipline would be more trustworthy than people trying to do so informally?
> than making a few simple, easy decisions about how to structure your programs.
Such as not using any third-party code which has not been fully audited to the statement? What a simple and easy decision that is.
It would look quite weird and most other programmers would think you were crazy for having done it, I think it's possible to write a C++ program that provably doesn't leak. You could define a custom operator new for every type which ensures that it gets allocated with some smart pointer or GC heap.
You could probably still use most of the C++ standard library that returns something needing manual de-allocation (except perhaps new and the old C malloc itself, which can be banned in various ways).
I was thinking of the weird tricks that would have to be in place to plausibly prove that there was no unmanaged dynamic allocation going on.
Scoff if you like, but consider you may not know everything there is to be learned about the craft.