shared adventures in the realm of thread-safety.

Jeremie Pelletier jeremiep at gmail.com
Sun Sep 13 15:08:57 PDT 2009


Robert Jacques Wrote:

> On Sun, 13 Sep 2009 15:04:57 -0400, Jeremie Pelletier <jeremiep at gmail.com>  
> wrote:
> [snip]
> > Unique data could only be used for aggregate properties, const/immutable  
> > data would also be implicitly unique. This qualifier alone would  
> > simplify shared quite a lot, allowing the use of unshared objects in  
> > shared contexts safely.
> 
> Neither const nor immutable data can be considered unique. First, any  
> const data may be being mutated by another routine, so it can't be safely  
> accessed without synchronization. Second, unique data is mutable while  
> const/immutable data is not. Third, most implementations of unique allow  
> for deterministic memory reclamation, which isn't possible if the unique  
> data might actually be const/immutable.

Good points, I can only agree with you here. However I still believe immutable data should be able to be used in shared contexts without being 'shared' or protected by a monitor.

> > The compiler should make the distinction between shared code and shared  
> > data and allow both shared and unshared instances to use shared methods,  
> > just like both const and mutable instances may call const methods. An  
> > error should also be triggered when calling a shared method of a shared  
> > object without synchronization, and maybe have a __sync keyword to  
> > override this. If a synchronized method is called from a non-shared  
> > object, no synchronization takes place.
> 
> I think you have the wrong paradigm in mind. Shared and non-shared aren't  
> mutable and const. They're mutable and immutable. From a technical  
> perspective, synchronization of shared methods are handled by the callee,  
> so there is no way not to call them and non-shared objects don't have a  
> monitor that can be synchronized. Now you can have the compiler use the  
> same code to generate two different object types (vtables, object layouts,  
> etc) with have the same interface, but that doesn't sound like what you're  
> suggesting.

I know that shared/unshared is not const/mutable. What I meant is that right now in D if a method is 'shared' it cannot be called from a non-shared object, which makes unshared instance of the class unusable without plenty of dirty casts. Take the following objects:

class Foo { void foo() const; }
class Bar { void bar() shared; }

Foo foo; foo.foo(); // ok, mutable object can call const method
Bar bar; bar.bar(); // error, unshared object may not call shared method

I had only presented the concept, your idea of using two virtual tables for shared/unshared instances is also what I had in mind for the implementation, and it would give exactly the behavior I had in mind. 

> > Allow me to illustrate my point with some code:
> >
> > class Foo {
> >     int bar() shared { return a; }
> >     __sync bar2() { synchronized(this) return a; }
> >     synchronized void foo() { a = 1; }
> >     int a;
> > }
> > auto foo1 = new shared(Foo)();
> > auto foo2 = new Foo;
> >
> > foo1.foo(); // ok, synchronized call
> > synchronized(foo1) foo1.foo(); // warning: recursive synchronization
> 
> Why a warning? Monitors are designed to handle recursive synchronization.

Its a performance issue that can easily be avoided, but still generates valid code.
 
> > foo2.foo(); // ok, unsynchronized call
> > synchronized(foo2) foo2.foo(); // ok synchronized call
> >
> > foo1.bar(); // error, unsynchronized call to bar() shared
> > synchronized(foo1) foo1.bar(); // ok, synchronized call
> > foo2.bar(); // ok, unsynchronized call
> > synchronized(foo1) foo1.bar(); // ok, synchronized call
> >
> > foo1.bar2(); // ok, method handles synchronized
> > synchronized(foo1) foo1.bar2(); // warning, recursive synchronization
> > foo2.bar2(); // ok, method handles synchronized, even on unshared object
> > synchronized(foo2) foo2.bar2(); // warning, recursive synchronization,  
> > even on unshared object
> >
> > That's about it, I believe this behavior would allow quite a number of  
> > multi-threaded techniques to be easily implemented and documented. It  
> > would only be the most natural thing since its quite similar to how  
> > const works.
> 
> The major benefit of const isn't method declaration, but object use: i.e.  
> only having to declare func(const T var) and not func(immutable T var) and  
> func(T var). Currently, there's no planned type to fill this role though  
> there have been some proposals.

I disagree, I think const methods are just as useful as const objects, since they are the only methods that can be called on such objects. They do not however prevent you from calling them on a mutable object. This is the behavior I want with shared too; unshared objects should be able to call shared methods, but shared objects should only be able to call shared methods.

> P.S. Shouldn't 'a' be either private or protected?

It should, but this was just an example ;)

> P.S.S. Bartosz Milewski has a good series of blogs on multi-threading  
> (with an eye on how to do it well in D).

I know, this is what sparked my interest for shared in the first place, I really look forward to implement most of his ideas in my runtime, but I am waiting until shared gets better semantics.

> Bike-shed: I've always preferred the CSP/pi-calculas term 'mobile' for the  
> concept of 'unique'. I think mobile better expresses the concept with  
> regard to multi-threading, where mobile is used to cheaply transfer data  
> between threads (i.e. it moves around/can move between threads, but isn't  
> shared between them). I find 'unique' to mainly convey the memory storage  
> aspect of the concept, which is less important outside of C/C++.

Maybe this is where 'volatile' could come back, from what I know it's still a reserved keyword in D and would fit nicely this purpose.



More information about the Digitalmars-d mailing list