On 15/04/2011 8:32 AM, Marijn Haverbeke wrote:
They have pointers into a heap shared with other tasks in their thread. We'd
have to dig through that heap cloning everything they point to.
Right. I can see the costs, but you have to agree that migrating tasks
would be a *great* thing to have. Having shared values become more
complicated might be worth it. Unique boxes are one good solution. You
also alluded to a task-lifetime trick last week (task X holding onto
immutable value Z, and not being allowed to die until task Y, which
accesses this value, finishes -- if I understood it correctly). There
are probably other hacks that can be applied when sharing big
structures. For small ones, copying is a good idea anyway.
It's entirely possible to go down this road. I'm much, much more
comfortable if our research leads us to a design in which:
- Domains don't exist.
- Tasks are run on threads M:N where, at the limit, you may
choose to make that 1:1. But if you have cost reasons to prefer
different M and N you can get it, nothing breaks.
- Tasks always own all their reachable data, no sharing.
- Messages are therefore always either deep-copied or moved.
That's an ok cognitive model. Fewer parts, fewer corner cases; it gives
up one case (shared messages) but might be a net win given the
simplification. Might, might not. Maybe that's all Rafael was suggesting
in the first place, or close enough not to matter.
I wasn't sure what we were pushing toward; a conclusion like "we have to
use unsafe blocks everywhere" is unacceptable to me. So is one where we
lose important parts of the per-task structure like its local GC pool,
incoming lockless queues for ports, or its unwind semantics on failure.
These are strict improvements to the notion of a thread, and they're
hard ones for users to simulate.
Losing the ability to share message substructures after sending is .. a
cost though. And it's one that's worth caring at least somewhat about;
maybe we sacrifice it but I want everyone to know why it's worth
keeping, so I will place it in a Special Attention-Getting Block:
I want users to feel comfortable making lots of tasks, not just for
concurrency: it's a way to *isolate* code from side effects of other
code. Even if I was developing a completely serial program I'd want
to be able to carve it up into tasks. They are natural boundaries,
like namespaces or such, where you have a line drawn that the language
is telling you the semantics will prevent anyone from crossing, even
considering dynamic reachability. That's important for maintaining
partial correctness and system function in the presence of errors.
That said, Erlang seems able to encourage users to make lots of tasks --
for robustness -- while having them pay for deep copies every time. So
maybe it's just something where users get accustomed to the copy
boundaries and learn to live with that tax. And maybe, if we have unique
pointers, most serious code will lean on them heavily so that ownership
handoff is more common. I'm unsure.
Of course, this'd also upset our current design of domains and such.
I'm not really putting myself behind any new approach at this point,
but I think we should definitely be open to anything that would help
us avoid costly and awkward I/O multiplexing magic.
Yeah .. I'm not wedded to domains; they seemed necessary to
differentiate cases, but if those cases wind up collapsing (and if,
absent the effect system, there's no reason for *processes* to be
reified in the language either) then removing the domain concept lowers
cognitive costs, while removing an awkward case (task starvation), so
I'm ... tentatively ok with it. If users will accept the loss of cheap
isolation in exchange for simplified model, and we don't run into I/O
scalability issues. But regarding that, here is another Attention
Getting Block:
Another thing to keep in mind: "awkward I/O multiplexing magic" is
likely *necessary* on some platforms to scale well. Or at least this
is the mythology. This is a numeric question that demands research.
Try writing a C program that does "the smallest thread you can make"
on each OS and tries to make 100,000 of them doing concurrent
blocking reads on 100,000 file descriptors. See if it scales as well
as a 100,000 way IOCP/kqueue/epoll approach. It might. It might not.
Kernel people are always tilting the balance one way or another,
sometimes userspace is just misinformed, working on old information.
Even with a 4kb (1 page) stack, I'd expect to be able to make a
million tasks on an 8gb machine. I .. actually demo'ed this on the old
rustboot approach, way back when, when tasks started with 300 bytes of
stack, it's plausible.
So: if you want to pursue this simplification, go forth and research!
See what our limits are. Otherwise we're making stuff up based on
folklore and blog posts.
-Graydon
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev