www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Message Passing and Shared Data

reply Andrew Wiley <debio264 gmail.com> writes:
I'm working on an application that makes use of message passing to
separate things that need to be responsive from tasks that may take
some time, and I'm having trouble because my message passing
discipline isn't meshing with D's type system. I'm pretty much
following the contract that when I send a message from one thread to
another, the original thread doesn't store any references to the
message or what's in it, IE ownership is transferred. The receiving
thread may then send some of the same objects back after some
processing, but again, ownership is transferred.
This has the benefit that I avoid a lot of memory allocations, but it
means that right now, I'm constructing everything as shared, then
casting it away to populate the object, then sending the object, then
casting shared away again in the receiver, and so on.
The messages are generally passed around for things like requesting a
large chunk of data that takes a while to generate and receiving
objects from an IO thread that's decoding objects from sockets.

Any suggestions as to how I could avoid doing casts everywhere and/or
restore some sort of safety while avoiding having to reallocate every
object I mess with? I've tried fiddling with __gshared, but couldn't
figure out any way to use it with message passing.
Apr 10 2011
parent reply dsimcha <dsimcha yahoo.com> writes:
On 4/11/2011 12:45 AM, Andrew Wiley wrote:
 I'm working on an application that makes use of message passing to
 separate things that need to be responsive from tasks that may take
 some time, and I'm having trouble because my message passing
 discipline isn't meshing with D's type system. I'm pretty much
 following the contract that when I send a message from one thread to
 another, the original thread doesn't store any references to the
 message or what's in it, IE ownership is transferred. The receiving
 thread may then send some of the same objects back after some
 processing, but again, ownership is transferred.
 This has the benefit that I avoid a lot of memory allocations, but it
 means that right now, I'm constructing everything as shared, then
 casting it away to populate the object, then sending the object, then
 casting shared away again in the receiver, and so on.
 The messages are generally passed around for things like requesting a
 large chunk of data that takes a while to generate and receiving
 objects from an IO thread that's decoding objects from sockets.

 Any suggestions as to how I could avoid doing casts everywhere and/or
 restore some sort of safety while avoiding having to reallocate every
 object I mess with? I've tried fiddling with __gshared, but couldn't
 figure out any way to use it with message passing.

This is one of the biggest weaknesses of std.concurrency's model. Moving, as opposed to sharing, data between threads is perfectly safe but is difficult/impossible to statically check, especially when multiple levels of pointer indirection are involved. I understand there are some D3-ish proposals to deal with this by extending the type system. I haven't considered these both because I want to solve the problem in the context of D2 and because D2's type system is already way too complex and I think piling more on is a terrible idea. I've been thinking about ways to make moving checkable at runtime, where checking is simpler and less conservative. What type of objects are you working with, general classes or containers? In the case of containers whose members don't have unshared aliasing (i.e. are either immutable, shared or don't have pointer indirection), I think the solution is sealed containers and reference counting. (For newcomers, a sealed container is one that does not allow escaping pointers/references to its members.) A sealed container whose members don't have unshared aliasing can be safely moved between threads if its reference count is 1. We would define a primitive called moveSend that takes a sealed container by reference, checks the reference count, puts it in the receiving thread's message queue, destroys the sending thread's view, then returns. Before we do this, though, we need a full set of sealed containers. In the case of general classes, I don't have a good answer. I think some answers were proposed back in the initial discussion on D's concurrency model, but they involved making D's type system more complex (not recommended). The problem is that virtual functions could be overridden to escape references to class members. It may be possible to make moving class instances checkable without a more complicated type system under some restrictive circumstances: 1. Create a RefCounted wrapper for classes and require that the reference count be 1. 2. Introspect the class to verify it's sealed: a. Require that the class have no public fields. b. Require that all methods be weakly pure (can't write to globals), return types with no unshared aliasing and take only arguments that are const or don't have unshared aliasing. The last requirement prevents escaping the address of a member variable to a field of an argument. c. This still fails miserably in the case of multiple levels of indirection. We could disallow them, make them also reference counted, or maybe come up with some other kludge.
Apr 11 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
dsimcha:

 and because D2's type system is already way 
 too complex and I think piling more on is a terrible idea.

(To me D type system seems less complex than the Scala one). To implement this type system feature you need some work and time, and it's not nice to waste this work, but here I'd like this type system extension to be optionally available in D (active with a compilation switch), to try it and see how much good/bad it is, to refine its design and find a possible acceptable one, etc. In Haskell there are several such optional extensions: http://cvs.haskell.org/Hugs/pages/hugsman/exts.html Bye, bearophile
Apr 11 2011
parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from bearophile (bearophileHUGS lycos.com)'s article
 dsimcha:
 and because D2's type system is already way
 too complex and I think piling more on is a terrible idea.


work, but here I'd like this type system extension to be optionally available in D (active with a compilation switch), to try it and see how much good/bad it is, to refine its design and find a possible acceptable one, etc.
 In Haskell there are several such optional extensions:
 http://cvs.haskell.org/Hugs/pages/hugsman/exts.html
 Bye,
 bearophile

Yes and this is why Haskell is widely regarded as "too academic". (I don't know as much about Scala.) The problem with fancy type system solutions is that they will always be conservative and making them less conservative seems to quickly increase their complexity. The halting problem makes a lot of decisions undecidable in the general case and even where they're in principle decidable, interprocedural analysis is hard. D's type system often relies on programmer discipline to propagate "local" information to a "global" level by using the proper type constructors and annotations. This scales about as well as the discipline to do things by convention. The result is that the type system very frequently gets in the way and nags the programmer with pedantic issues rather than helping, because someone forgot to mark something with the proper annotation.
Apr 11 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
dsimcha:

 Yes and this is why Haskell is widely regarded as "too academic".

Haskell is somewhat academic (despite being WAY more widespread and used than D), but not because it has experimental features switch-able at compile time :-) Regarding type system features, I think the pure annotation is one of the best things added to D2. When a language like D has static typing, I want the full advantages of it (like good type "annotations" enforced at compile time), otherwise I'm fine with Python. Bye, bearophile
Apr 11 2011
parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from bearophile (bearophileHUGS lycos.com)'s article
 dsimcha:
 Yes and this is why Haskell is widely regarded as "too academic".


 Regarding type system features, I think the pure annotation is one of the best

advantages of it (like good type "annotations" enforced at compile time), otherwise I'm fine with Python.
 Bye,
 bearophile

Ok, I guess this is just a fundamental world view difference. Some programmers (such as yourself) actually **like** static typing. I'm not a huge fan of it, but I consider it a necessary tradeoff if you want a language that generates efficient code and allows you to do low-level work. IMHO D's killer feature (and the reason I use it in the first place) is that its generic programming facilities allow you to have these while avoiding most of the rigidity and annoyingness of static typing. I especially dislike the "fancy" type system features because they interact so poorly with generic code. (Yes, this problem is in principle fixable but it's a drain on our limited manpower and doesn't seem to be getting fixed in practice.) If you could somehow get both efficient code and the ability to do low-level work in a dynamically typed language (the first is theoretically possible but insanely hard in practice, the second is arguably impossible since dynamic typing is so contrary to how the machine works at the lowest levels), I'd take a dynamically typed language any day of the week.
Apr 11 2011
parent bearophile <bearophileHUGS lycos.com> writes:
dsimcha:

 Ok, I guess this is just a fundamental world view difference.

Right. The world has so many different computer languages also because programmers see programming in different ways :-) (And if we are both using D2 this means we share part of that world view.)
 Some programmers (such as yourself) actually **like** static typing.

I am writing lot of code both in dynamically typed and statically typed languages, so I like about equally both. If a language is well designed, it gives me different advantages, that pay back for the disadvantages of the language: - A well designed dynamically typed language is quick to write, flexible, allows short code, is never fussy regarding types, allows me to write Pseudocode-like code with a clean syntax, avoids many problems using higher level constructs, etc. - A good statically typed language gives more runtime efficiency, but its type quite flexible system must give me something more back, many more static guarantees about the correctness of the code (Java is not good enough on this). This way, the static typing has paid me back enough to compensate the time needed to get the types right, to add those annotations, the reduced flexibility, etc.
I especially dislike the "fancy" type system features because they interact so
poorly with generic code.  (Yes, this problem is in principle fixable but it's
a drain on our limited manpower and doesn't seem to be getting fixed in
practice.)<

Designing a very flexible static type system (and those "fixes" for the generic code) is quite harder than designing a dynamically typed language. D design surely needs to fix and improve some more of those (auto ref, inout, switchable purity and non-throw-ness, etc).
If you could somehow get both efficient code and the ability to do low-level
work in a dynamically typed language (the first is theoretically possible but
insanely hard in practice, the second is arguably impossible since dynamic
typing is so contrary to how the machine works at the lowest levels), I'd take
a dynamically typed language any day of the week.<

When you look at languages like ATS, Lua-JIT, BitC, Haskell, and D, you see the design space of programming languages is very wide, so much that we're far from having explored a significant percentage of it. My guess is what you ask for will eventually become possible. Bye, bearophile
Apr 11 2011