[dmd-concurrency] draft 6
Andrei Alexandrescu
andrei at erdani.com
Thu Jan 28 08:18:48 PST 2010
Thanks for the comments! A few follow-ups below.
Michel Fortin wrote:
> I'm curious about something. You say this:
>
>> When receive sees a message of an unexpected type, it doesn’t throw
>> an excep- tion (as receiveOnly does). The message passing subsystem
>> simply saves the non- matching messages in a queue, colloquially
>> known as the thread’s mailbox.
>
>> [...]
>>
>> Planting a Variant handler at the bottom of the message handling
>> food chain is a good method to dynamically check for protocol bugs.
>>
>
> So basically you should use receive with a variant at the end when
> you want to catch protocol bugs (and avoid crowding the queue). My
> question is this: when do you *not* want to catch protocol bugs?
Heh. Good point. I should rephrase that. The point is that sometimes you
call receive() without a Variant handler because you want to leave
non-matching messages in the mailbox, in knowledge that you'll handle
them later. Here's the rephrase:
=========
Planting a\sbs @Variant@ handler at the bottom of the message handling
food chain is a good method to make sure that stray messages aren't
left in your mailbox.
=========
>> Choosing a good buffer size and checking for errors completes a
>> useful (if unoriginal) program.
>
>
> Hum, choosing a good buffer size is a useful "program"?
=========
Adding appropriate error handling completes a useful (if unoriginal)
program.
=========
>> The exception is only thrown if receive has no more match- ing
>> messages and must wait for a new message; as long as receive has
>> something to fetch from the mailbox, it will not throw. In other
>> words, when the owner thread termi- nates, the owned threads’ calls
>> to receive (or receiveOnly for that matter) will throw
>> OwnerTerminated if and only if they would otherwise block waiting
>> for a new message
>
>
> Didn't we agree this should happen serially, in he same order the
> messages are added to the queue? Ah, but you're explaining it
> correctly two paragraphs below. :-)
>
>
>> The ownership relation is not necessarily unidirectional. In fact,
>> two threads may even own each other; in that case, whichever thread
>> finishes will notify the other.
>
>
> My idea with the thread termination protocol was that it would
> prevent circular references, following the owner chain would always
> lead you back to the main thread. It somewhat breaks the idea that
> when the main thread terminates all threads end up notified. I don't
> quite oppose this, but which use case do you have in mind for this?
I don't. I chose to do what Erlang does. It is quite clear on
bidirectional linking and in fact makes it the default, so it must have
a reason. Sean?
>> the OwnerTerminated exception
>
> So it is going to inherit from Exception after all?
Yah, I think it should.
>> If a thread exits via an exception, the exception OwnerFailed
>> propagates to all of its owned threads by means of prioritySend.
>
>
> I though we were in agreement that it'd be better if this was handled
> serially by default to avoid races in the program's logic?
I think it's fine to propagate serially on _success_ but not on failure.
Failure has priority.
> Also, no mention about what happens if the writer thread exits with
> an exception. You only explain what happens if it exits normally
> after catching the exception itself (sending a message to that thread
> throws).
I (only) explained what happens if the writer throws:
=========
In this case, @fileWriter@ returns peacefully when @main@ exits and
everyone's happy. But what happens in the case the secondary
thread---the writer---throws an exception? The call to the @write@
function may fail if there's a problem writing data to @tgt at . In that
case, the call to @send@ from the primary thread will fail by throwing
an exception, which is exactly what should happen.
=========
I changed the paragraph to:
=========
In this case, @fileWriter@ returns peacefully when @main@ exits and
everyone's happy. But what happens in the case the secondary
thread---the writer---throws an exception? The call to the @write@
function may fail if there's a problem writing data to @tgt at . In that
case, the call to @send@ from the primary thread will fail by throwing
an\sbs @OwnedFailed@ exception, which is exactly what should
happen. By the way, if an owned thread exits normally (as opposed to
throwing an exception), subsequent calls to @send@ to that thread also
fail, just with a different exception type:\sbs @OwnedTerminated at .
=========
By the way I added a joke that I don't want you guys to miss:
=========
As an aside, if there exists a ``Best Form-Follows-Function'' award,
then the notation @qualifier(type)@ should snatch it. It's perfect.
You can't even syntactically create the wrong pointer type, because it
would look like this:
\begin{D-nocheck}
int shared(*) pInt;
\end{D-nocheck}
\noindent which does not make sense even at syntactic level because
`@*@' is not a type (granted, it \emph{is} a nice emoticon for a
cyclops).
=========
> On to 'shared'...
>
>> The annotation helps the compiler with much more than an indication
>> of where the variable should be allocated
>
>
> Strange. I though 'shared' has no relation to where the data is
> allocated. There is no way to implement thread-local memory pools in
> the D2, since you're allowed to cast non-shared to shared when you
> know that no one else has a reference to it, and also because of
> immutable. So what are you trying to say exactly?
For global data, shared indicates that data goes in the global memory
segment, not in TLS. To avoid confusion, I just changed that to read:
"The annotation helps the compiler a great deal: ..."
>> atomicOp!"+="(threadsCount, 1); // fine
>
>
> Oh, wow. Can't we have a better syntax? :-/
I know you'd rather define the Atomic type. I think it's better to have
explicit operations, and atomicOp!"+=" is better than a million
atomicBlahs. Let's collect some more opinions.
>> It’s like saying “I’ll share this wallet with everyone, just please
>> re- member that the money in it ain’t shared.” Claiming the pointer
>> is shared across threads but the pointed-to data is not takes us
>> back to the wonderful programming-by-honor- system paradigm that
>> has failed so successfully throughout history. It’s not the volun-
>> tary malicious uses, it’s the honest mistakes that form the bulk of
>> problems. Software is large, complex, and ever-changing, traits
>> that never go well with maintaining guarantees through convention.
>
> I think you're going a little too far with this. Just saying "if many
> threads can read a pointer, all those threads can follow this pointer
> and access the data it points to, so that data cannot be shared"
> should be enough. It's pretty obvious.
>
> And I'm not even sure how far the wallet analogy goes since
> const(shared(money*)) should be enough to not have anyone steal your
> money (you should keep the mutable pointer to yourself, of course).
I like that paragraph too much, so I'll exercise author's prerogative on
that. Your combination of qualifiers does complete the joke very well,
so I added this footnote:
========
Incidentally, you can share a wallet with theft-protected money with
the help of \cc{const} by using the type \cc{shared(const(Money)*)}.
========
>> The shared constructor undergoes special typechecking, distinct
>> from that of reg- ular functions. The compiler makes sure that
>> during construction the address of the object or of a member of it
>> does not escape to the outside. Only at the end of the con-
>> structor, the object is “published” and ready to become visible to
>> multiple threads.
>
> That sounds wrong. I mean, it's all fine to have a constructor that
> ensures that no reference escapes, but is 'shared' the right term for
> this? In all other cases, 'shared' means the 'this' pointer is
> shared, not that it can't escape.
I am not sure what the rules surrounding shared constructors should be.
Inside the ctor, the object is not yet shared, so for example member
arrays should be initializable, just not with aliased arrays.
> I think 'scope' would be a better term for 'no escape'.
>
> Also, I'm a little surprised. I though the 'no escape' thing was
> deemed too difficult to implement a few months ago. Has something
> changed?
Nothing has changed. Inside immutable and shared constructors, we are
limiting what the constructors can do so we enable analysis without
requiring inter-procedural reach.
Andrei
More information about the dmd-concurrency
mailing list