www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Synchronized Classes and Struct Members

reply Andrew Wiley <wiley.andrew.j gmail.com> writes:
Geez, I try to write some multithreaded code and I just keep hitting these:
--------
module test3;

struct SomeData {
int i;
 int iPlus2() {
return i + 2;
 }
}

synchronized class Bob {
private:
SomeData _dat;
 public:
this() {
_dat.i = 3;
 }
  property
 int i() {
return _dat.iPlus2(); // test3.d(22): Error: function test3.SomeData.iPlus2
() is not callable using argument types () shared
 }
}
--------

This seems like it should be legal because SomeData is a value type.
Accessing _dat.i directly is legal, and _dat can't possibly be shared. If
I'm understanding things correctly, transitive shared shouldn't apply to
value types like this, so the type of "this" when calling iPlus2 should just
be SomeData.
Oct 24 2011
parent reply Benjamin Thaut <code benjamin-thaut.de> writes:
Am 25.10.2011 08:06, schrieb Andrew Wiley:
 Geez, I try to write some multithreaded code and I just keep hitting these:
 --------
 module test3;

 struct SomeData {
 int i;
 int iPlus2() {
 return i + 2;
 }
 }

 synchronized class Bob {
 private:
 SomeData _dat;
 public:
 this() {
 _dat.i = 3;
 }
  property
 int i() {
 return _dat.iPlus2(); // test3.d(22): Error: function
 test3.SomeData.iPlus2 () is not callable using argument types () shared
 }
 }
 --------

 This seems like it should be legal because SomeData is a value type.
 Accessing _dat.i directly is legal, and _dat can't possibly be shared.
 If I'm understanding things correctly, transitive shared shouldn't apply
 to value types like this, so the type of "this" when calling iPlus2
 should just be SomeData.
It can't be garantueed that a pointer to _dat is not given away at some point in the program. For example you could have a clas Foo that inherits from Bob and has a getter for _dat. Then it wouldn't be correct anymore to sasume _dat is unshared. Because of that it has been decided that trying to figure out if _dat has to be shared or not will not happen, thus its just shared always. I've already reported this some time ago and essentially it makes synchronized classes useless for me. I usually just write a normal class and but the synchronized blocks in myself. Also I use __gshared ;-) -- Kind Regards Benjamin Thaut
Oct 24 2011
parent reply Andrew Wiley <wiley.andrew.j gmail.com> writes:
On Tue, Oct 25, 2011 at 1:26 AM, Benjamin Thaut <code benjamin-thaut.de>wrote:

 Am 25.10.2011 08:06, schrieb Andrew Wiley:

  Geez, I try to write some multithreaded code and I just keep hitting
 these:
 --------
 module test3;

 struct SomeData {
 int i;
 int iPlus2() {
 return i + 2;
 }
 }

 synchronized class Bob {
 private:
 SomeData _dat;
 public:
 this() {
 _dat.i = 3;
 }
  property
 int i() {
 return _dat.iPlus2(); // test3.d(22): Error: function
 test3.SomeData.iPlus2 () is not callable using argument types () shared
 }
 }
 --------

 This seems like it should be legal because SomeData is a value type.
 Accessing _dat.i directly is legal, and _dat can't possibly be shared.
 If I'm understanding things correctly, transitive shared shouldn't apply
 to value types like this, so the type of "this" when calling iPlus2
 should just be SomeData.
It can't be garantueed that a pointer to _dat is not given away at some point in the program. For example you could have a clas Foo that inherits from Bob and has a getter for _dat. Then it wouldn't be correct anymore to sasume _dat is unshared. Because of that it has been decided that trying to figure out if _dat has to be shared or not will not happen, thus its just shared always. I've already reported this some time ago and essentially it makes synchronized classes useless for me. I usually just write a normal class and but the synchronized blocks in myself. Also I use __gshared ;-)
Alright, but why can I access _dat.i without synchronization?
Oct 24 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/25/2011 08:36 AM, Andrew Wiley wrote:
 On Tue, Oct 25, 2011 at 1:26 AM, Benjamin Thaut <code benjamin-thaut.de
 <mailto:code benjamin-thaut.de>> wrote:

     Am 25.10.2011 08:06, schrieb Andrew Wiley:

         Geez, I try to write some multithreaded code and I just keep
         hitting these:
         --------
         module test3;

         struct SomeData {
         int i;
         int iPlus2() {
         return i + 2;
         }
         }

         synchronized class Bob {
         private:
         SomeData _dat;
         public:
         this() {
         _dat.i = 3;
         }
          property
         int i() {
         return _dat.iPlus2(); // test3.d(22): Error: function
         test3.SomeData.iPlus2 () is not callable using argument types ()
         shared
         }
         }
         --------

         This seems like it should be legal because SomeData is a value type.
         Accessing _dat.i directly is legal, and _dat can't possibly be
         shared.
         If I'm understanding things correctly, transitive shared
         shouldn't apply
         to value types like this, so the type of "this" when calling iPlus2
         should just be SomeData.


     It can't be garantueed that a pointer to _dat is not given away at
     some point in the program. For example you could have a clas Foo
     that inherits from Bob and has a getter for _dat. Then it wouldn't
     be correct anymore to sasume _dat is unshared. Because of that it
     has been decided that trying to figure out if _dat has to be shared
     or not will not happen, thus its just shared always. I've already
     reported this some time ago and essentially it makes synchronized
     classes useless for me. I usually just write a normal class and but
     the synchronized blocks in myself. Also I use __gshared ;-)


 Alright, but why can I access _dat.i without synchronization?
_dat.i is shared by transitivity. You can access shared variables without synchronization (according to TDPL, memory barriers are inserted automatically, but I don't know if DMD implements this). SomeData.iPlus2's implicit 'this' parameter is not shared.
Oct 25 2011
parent reply Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
Yes. The shared-ness, const-ness or immutable-ness of the _this_
parameter is defined by marking the method itself shared, const or
immutable respectively.
But marking the method shared or immutable makes that method callable
_ONLY_ for shared or immutable objects of that class or struct
respectively.
In order to make that struct usable from both shared and non-shared
contexts, you need to have 2 overloads of that method: shared and
non-shared.

On Tue, Oct 25, 2011 at 4:22 PM, Timon Gehr <timon.gehr gmx.ch> wrote:
 On 10/25/2011 08:36 AM, Andrew Wiley wrote:
 On Tue, Oct 25, 2011 at 1:26 AM, Benjamin Thaut <code benjamin-thaut.de
 <mailto:code benjamin-thaut.de>> wrote:

 =A0 =A0Am 25.10.2011 08:06, schrieb Andrew Wiley:

 =A0 =A0 =A0 =A0Geez, I try to write some multithreaded code and I just k=
eep
 =A0 =A0 =A0 =A0hitting these:
 =A0 =A0 =A0 =A0--------
 =A0 =A0 =A0 =A0module test3;

 =A0 =A0 =A0 =A0struct SomeData {
 =A0 =A0 =A0 =A0int i;
 =A0 =A0 =A0 =A0int iPlus2() {
 =A0 =A0 =A0 =A0return i + 2;
 =A0 =A0 =A0 =A0}
 =A0 =A0 =A0 =A0}

 =A0 =A0 =A0 =A0synchronized class Bob {
 =A0 =A0 =A0 =A0private:
 =A0 =A0 =A0 =A0SomeData _dat;
 =A0 =A0 =A0 =A0public:
 =A0 =A0 =A0 =A0this() {
 =A0 =A0 =A0 =A0_dat.i =3D 3;
 =A0 =A0 =A0 =A0}
 =A0 =A0 =A0 =A0 property
 =A0 =A0 =A0 =A0int i() {
 =A0 =A0 =A0 =A0return _dat.iPlus2(); // test3.d(22): Error: function
 =A0 =A0 =A0 =A0test3.SomeData.iPlus2 () is not callable using argument t=
ypes ()
 =A0 =A0 =A0 =A0shared
 =A0 =A0 =A0 =A0}
 =A0 =A0 =A0 =A0}
 =A0 =A0 =A0 =A0--------

 =A0 =A0 =A0 =A0This seems like it should be legal because SomeData is a =
value
 type.
 =A0 =A0 =A0 =A0Accessing _dat.i directly is legal, and _dat can't possib=
ly be
 =A0 =A0 =A0 =A0shared.
 =A0 =A0 =A0 =A0If I'm understanding things correctly, transitive shared
 =A0 =A0 =A0 =A0shouldn't apply
 =A0 =A0 =A0 =A0to value types like this, so the type of "this" when call=
ing iPlus2
 =A0 =A0 =A0 =A0should just be SomeData.


 =A0 =A0It can't be garantueed that a pointer to _dat is not given away a=
t
 =A0 =A0some point in the program. For example you could have a clas Foo
 =A0 =A0that inherits from Bob and has a getter for _dat. Then it wouldn'=
t
 =A0 =A0be correct anymore to sasume _dat is unshared. Because of that it
 =A0 =A0has been decided that trying to figure out if _dat has to be shar=
ed
 =A0 =A0or not will not happen, thus its just shared always. I've already
 =A0 =A0reported this some time ago and essentially it makes synchronized
 =A0 =A0classes useless for me. I usually just write a normal class and b=
ut
 =A0 =A0the synchronized blocks in myself. Also I use __gshared ;-)


 Alright, but why can I access _dat.i without synchronization?
_dat.i is shared by transitivity. You can access shared variables without synchronization (according to TDPL, memory barriers are inserted automatically, but I don't know if DMD implements this). SomeData.iPlus2'=
s
 implicit 'this' parameter is not shared.
Oct 25 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/25/2011 02:33 PM, Gor Gyolchanyan wrote:
 Yes. The shared-ness, const-ness or immutable-ness of the _this_
 parameter is defined by marking the method itself shared, const or
 immutable respectively.
 But marking the method shared or immutable makes that method callable
 _ONLY_ for shared or immutable objects of that class or struct
 respectively.
 In order to make that struct usable from both shared and non-shared
 contexts, you need to have 2 overloads of that method: shared and
 non-shared.
Yes, indeed: One that is efficient and correct in an unshared context and one that _actually works_ if sharing is going on. The two D implementations are the same only in toy examples, and since memory barriers have to be inserted for the shared one, the two methods necessarily compile to different machine code.
Oct 25 2011
next sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
And so you can have both thread-safe synchronized heavy-duty container
and a fast and small container all in one just by overloading the
appropriate methods and adding appropriate synchronization blocks in
the shared ones.
This is one of those "little" advantages of D over C++, that make my
life _SO_ much easier.


On Tue, Oct 25, 2011 at 4:44 PM, Timon Gehr <timon.gehr gmx.ch> wrote:
 On 10/25/2011 02:33 PM, Gor Gyolchanyan wrote:
 Yes. The shared-ness, const-ness or immutable-ness of the _this_
 parameter is defined by marking the method itself shared, const or
 immutable respectively.
 But marking the method shared or immutable makes that method callable
 _ONLY_ for shared or immutable objects of that class or struct
 respectively.
 In order to make that struct usable from both shared and non-shared
 contexts, you need to have 2 overloads of that method: shared and
 non-shared.
Yes, indeed: One that is efficient and correct in an unshared context and one that _actually works_ if sharing is going on. The two D implementations are the same only in toy examples, and since memory barriers have to be inserted for the shared one, the two methods necessarily compile to different machine code.
Oct 25 2011
prev sibling next sibling parent reply Andrew Wiley <wiley.andrew.j gmail.com> writes:
On Tue, Oct 25, 2011 at 8:06 AM, Gor Gyolchanyan <
gor.f.gyolchanyan gmail.com> wrote:

 And so you can have both thread-safe synchronized heavy-duty container
 and a fast and small container all in one just by overloading the
 appropriate methods and adding appropriate synchronization blocks in
 the shared ones.
 This is one of those "little" advantages of D over C++, that make my
 life _SO_ much easier.
Except that overloading shared and non-shared methods is not allowed.
Oct 25 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/25/2011 06:24 PM, Andrew Wiley wrote:
 On Tue, Oct 25, 2011 at 8:06 AM, Gor Gyolchanyan
 <gor.f.gyolchanyan gmail.com <mailto:gor.f.gyolchanyan gmail.com>> wrote:

     And so you can have both thread-safe synchronized heavy-duty container
     and a fast and small container all in one just by overloading the
     appropriate methods and adding appropriate synchronization blocks in
     the shared ones.
     This is one of those "little" advantages of D over C++, that make my
     life _SO_ much easier.


 Except that overloading shared and non-shared methods is not allowed.
Yes it is. struct X{ string foo(){ return "unshared"; } string foo()shared{ return "shared"; } } void main(){ X x; shared X y; assert(x.foo() == "unshared"); assert(y.foo() == "shared"); }
Oct 25 2011
parent Andrew Wiley <wiley.andrew.j gmail.com> writes:
On Tue, Oct 25, 2011 at 11:59 AM, Timon Gehr <timon.gehr gmx.ch> wrote:

 On 10/25/2011 06:24 PM, Andrew Wiley wrote:

 On Tue, Oct 25, 2011 at 8:06 AM, Gor Gyolchanyan
 <gor.f.gyolchanyan gmail.com
<mailto:gor.f.gyolchanyan **gmail.com<gor.f.gyolchanyan gmail.com>>>
 wrote:

    And so you can have both thread-safe synchronized heavy-duty container
    and a fast and small container all in one just by overloading the
    appropriate methods and adding appropriate synchronization blocks in
    the shared ones.
    This is one of those "little" advantages of D over C++, that make my
    life _SO_ much easier.


 Except that overloading shared and non-shared methods is not allowed.
Yes it is. struct X{ string foo(){ return "unshared"; } string foo()shared{ return "shared"; } } void main(){ X x; shared X y; assert(x.foo() == "unshared"); assert(y.foo() == "shared"); }
Then it's been silently fixed since this was written: http://3d.benjamin-thaut.de/?p=18
Oct 25 2011
prev sibling parent reply Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
What? Are you sure? It's supposed to be! Sharedness of the method is
the sharedness of the _this_ parameter, which must cause overloading,
since shared-ness of a type is part of the type.
If so, it's definitely a bug.

On Tue, Oct 25, 2011 at 8:24 PM, Andrew Wiley <wiley.andrew.j gmail.com> wrote:
 On Tue, Oct 25, 2011 at 8:06 AM, Gor Gyolchanyan
 <gor.f.gyolchanyan gmail.com> wrote:
 And so you can have both thread-safe synchronized heavy-duty container
 and a fast and small container all in one just by overloading the
 appropriate methods and adding appropriate synchronization blocks in
 the shared ones.
 This is one of those "little" advantages of D over C++, that make my
 life _SO_ much easier.
Except that overloading shared and non-shared methods is not allowed.
Oct 25 2011
parent simendsjo <simendsjo gmail.com> writes:
On 25.10.2011 18:35, Gor Gyolchanyan wrote:
 What? Are you sure? It's supposed to be! Sharedness of the method is
 the sharedness of the _this_ parameter, which must cause overloading,
 since shared-ness of a type is part of the type.
 If so, it's definitely a bug.

 On Tue, Oct 25, 2011 at 8:24 PM, Andrew Wiley<wiley.andrew.j gmail.com>  wrote:
 On Tue, Oct 25, 2011 at 8:06 AM, Gor Gyolchanyan
 <gor.f.gyolchanyan gmail.com>  wrote:
 And so you can have both thread-safe synchronized heavy-duty container
 and a fast and small container all in one just by overloading the
 appropriate methods and adding appropriate synchronization blocks in
 the shared ones.
 This is one of those "little" advantages of D over C++, that make my
 life _SO_ much easier.
Except that overloading shared and non-shared methods is not allowed.
It works for simple cases at least: class C { int i() shared { return 1; } int i() { return 2; } } void main() { auto c1 = new C(); assert(c1.i() == 2); auto c2 = cast(shared)new C(); assert(c2.i() == 1); }
Oct 25 2011