On Sun, 13 Sep 2009 18:08:57 -0400, Jeremie Pelletier <[email protected]>
wrote:
Robert Jacques Wrote:
On Sun, 13 Sep 2009 15:04:57 -0400, Jeremie Pelletier
<[email protected]>
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.
One of the purposes behind immutable was lock-free access. As far as I
know you can use immutable data in shared contexts today without any other
modifiers. A quick test seems to indicate this works today, but if you've
got a test case where it doesn't, I'd recommend filing it as a bug.
> 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.
Bartosz took the concept one step further: when declared as shared, all
methods are implicitly wrapped in synchronize blocks. He then added a
keyword for more manual, lock-free style programming. But this syntactic
sugar isn't implemented yet.
> 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.
Really? Every public method that calls another public method (of the same
object) results in recursive synchronization. And if your example was
longer than a one liner, you'd also have to have recursive
synchronization. There are ways to reduce recursive synchronization, like
public wrappers of protected/private methods, but they are not always
appropriate or feasible for the use case. BTW, in general the threshold
for what's a warning in DMD is generally a lot higher than other compilers
(on the theory that if warnings are generated for every build you'll never
read them)
[snip]
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.
The volatile keyword has a very precise meaning in C/C++, which D altered
and then abandoned. I think using it for the concept of mobile/unique
would confusing. It also lacks any connotations related to a mobile/unique
type. (i.e. I don't see the logic behind the choice, besides the keyword
being unused)