View mode: basic / threaded / horizontal-split · Log in · Help
July 13, 2010
TDPL, shared data, and Phobos
This is really like 3 messages, but I didn't want to spam the list with topics, and maybe I'm the only one feeling this pain anyway. So first: from the perspective of a guy who has dabbled in D1 and D2 for years, but hasn't looked closed at the language in ~10 months, I loved TDPL. It convinced me that D2 is going to kick ass. Sadly though, I felt like I finished chapter 13 without gaining any real understanding of how `shared` works in D, and how I can effectively share mutable data when it *is* necessary. Maybe this was intentional, and shared is just too much of an experimental feature still to write a chapter on. But I decided to play around with some prototypes.

First, it took me ages to find any sign of any locking primitives beyond the implied Mutex in Object. Finally I resorted to doing a `grep -ri semaphore /usr/local/dmd2` (on OS X), and found a whole wealth of code. There's an entire core.sync.* package! My end goal was to find or create a read/write lock, and lo and behold, there's one all ready and containing unit tests in core/sync/rwmutex.d.

Which leads to my first questions: why are the core.* interfaces apparently not documented along with the std.* packages on the D web site? Is there a documentation resource elsewhere? Even if the DDOC is sparse, at least showing which classes exist under core.* would be a huge help. And are these implementations ready for use by my code, or are they hidden away for a reason?

Also, is there any documentation on the actual semantics of shared? http://www.digitalmars.com/d/2.0/attribute.html is a blank on the subject, and the "migrating to shared" article only talks about simple global state. What are the actual semantics of shared classes, and how do they interact with other code? For instance, after much banging of my head against the desk, I finally wrote a working implementation of a simple shared multi-reader var. Obviously there are better ways to do a simple shared incrementing counter, this is just a first experiment working toward a shared mutable 512MB trie data structure that we have in our app's current C++ implementation:

> shared class MyValue {
>     this() {
>         _lock = cast(shared)new ReadWriteMutex;
>     }
> 
>     int inc() {
>         synchronized((cast(ReadWriteMutex)_lock).writer) {
>             return _value++;
>         }
>     }
> 
>     int get() {
>         synchronized((cast(ReadWriteMutex)_lock).reader) {
>             return _value;
>         }
>     }
> 
>     private ReadWriteMutex _lock;
>     private int _value;
> }
> 
> shared MyValue sharedVal;
> ... seems to behave correctly with multiple threads reading and writing ...

So I can maybe understand the cast(shared) in the ctor. But I have to admit I have absolutely no idea why I had to cast away the shared attribute in the inc/get methods. Is there any documentation on what's really going on in the compiler here? It's a shared method, accessing a shared instance var, why the cast? Is the compiler upset about something in the definition of ReadWriteMutex itself?

Also, how would one implement this as a struct? My postblit op generates compiler errors about casting between shared/unshared MyValue:

> shared struct MyValue {
>    this(this) { _lock = cast(shared) new ReadWriteMutex; } // ERROR
>    ... same as above ...
> }

I recognize the possible race conditions here, but there has to be *some* way to implement a postblit op on a shared struct?

I hope this doesn't come across as empty complaining, I'm happy to help improve the documentation if I can.
Top | Discussion index | About this forum | D home