shared adventures in the realm of thread-safety.

Jeremie Pelletier jeremiep at gmail.com
Sun Sep 13 12:04:57 PDT 2009


Graham St Jack Wrote:

> I'm also having the same problems.
> 
> As Jeremie said, as soon as you start introducing shared methods (via 
> synchronized for example), you rapidly get into trouble that can only be 
> overcome by excessive casting.
> 
> It may be possible to contain the problem by refactoring multi-threaded 
> code so that the shared objects are very small and simple, but even then 
> the casting required is too much. This approach might be ok if you could 
> define classes as being shared or immutable, and ALL instance of them 
> were then implicitly shared or immutable. Also, immutable objects should 
> be implicitly shareable.

I agree, this is also one of my main concerns with shared in its current state. It's an amazing and powerful concept and has the potential to make multi-thread code much easier and safer to write. But all the required casting is killing the safety, and makes it harder to use than it would be not having shared at all. The lack of an unique qualifier certainly doesn't help either.

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.

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.

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
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.



More information about the Digitalmars-d mailing list