www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Shared method problem

reply "tcak" <tcak pcak.com> writes:
Whenever I start writing a class which would support "shared" 
methods, either there are lots of duplicate methods come into 
class, synchronisation code required.


PROBLEM 1: Duplication

Look at the code below:

class NamedPipe{
	private string filename;
	private int mode;

	public this(string filename, int mode){
		// cast is used to fix `shared` problem
		this.filename = cast( typeof(this.filename) )filename;
		this.mode = cast( typeof(this.mode) )mode;
	}
}

If I define the constructor WITHOUT "shared" keyword, and try to 
create a shared object as `auto pipe = new shared 
NamedPipe("/tmp/pipe", 432);`, compiler tells me, I can't do this 
with shared keyword. If I define the constructor WITH "shared" 
keyword, and try to create a non-shared object, again compiler 
gives error. So, it is pushing me to either duplicate constructor 
or do casting. Some of you may say `pure` function etc., though 
if you are writing a complex class, that may not be possible. So, 
duplication is inevitable.



PROBLEM 2: Synchronisation

Let's say I want to write a method that will do synchronisation 
ONLY IF the method is called from a shared object.

public void updateMode( int newMode ){
	// THIS IS NOT SHARED. CHANGE IT IMMEDIATELY.
	mode = newMode;
}

public void updateMode( int newMode ) shared{
	// THIS IS SHARED. SYNCHRONISE WHILE CHANGING.
	synchronized( this ){
		mode = newMode;
	}
}

The above code is good, but same method is duplicated again just 
to be able to enable synchronisation on shared method. So, 
non-shared method can run at much higher speed if it is to be 
called so many times.


CONCLUSION:

In the current implementation, duplication is almost inevitable, 
and if codes are long, then God help us. Long maintenance and 
error prone coding.

To be able to solve this, I thought a coding style as below:

public void foo(){
	static if( is(this: shared) ){
		// some of codes come here if the object is shared
	}
	else{
		// some of codes come here if the object is not shared
	}
}

But unfortunately it doesn't work in this way.

Is there really need to allow shared objects different methods, 
and non-shared objects different methods? Why don't we separate 
implementation instead of methods according to being shared?
Feb 12 2014
next sibling parent reply "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
To be concise: don't put all your eggs into one basket. If you 
want shared class - write shared class.

 Is there really need to allow shared objects different methods, 
 and non-shared objects different methods? Why don't we separate 
 implementation instead of methods according to being shared?

Exactly. Separate implementations. Completely. Shared is not just about "insert synchronized everywhere and be done with it". As a storage class, it tells the compiler "don't reorder accesses to this variable". Yes, as method attribute, it disallows you to call this method on an unshared reference. But that doesn't mean you now should rush to create both shared and non-shared methods for your classes: you'll be doing yourself a disservice. Design and implementation of concurrent data structures is very different from non-concurrent ones. Look up any concurrent data structure and see how drastically it differs from the plain, "single-threaded" one, even if it's built on lock-based synchronization, let alone lock-free. Providing two different implementations (that require different data for e.g. bookkeeping) inside one class is impractical and error-prone. If you're just going to synchronize every single method call for your class on a mutex, then you don't really need shared qualifier at all (well, you'll need to briefly cast the reference to shared to e.g. pass it to another thread). Also, I assume you understand that 'shared' and all its aspects are not yet fully realized in the language. Andrei mentioned that finalizing 'shared' should be made one of the primary goals. Maybe we would see some interesting syntactic solutions, type system improvements, etc. I think we already can help with some things (see e.g. my thread about shared sync primitives). But regardless, design your single-threaded and concurrent data separately, go easy on yourself :)
Feb 12 2014
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/12/14, 6:00 AM, Stanislav Blinov wrote:
 To be concise: don't put all your eggs into one basket. If you want
 shared class - write shared class.

 Is there really need to allow shared objects different methods, and
 non-shared objects different methods? Why don't we separate
 implementation instead of methods according to being shared?

Exactly. Separate implementations. Completely. Shared is not just about "insert synchronized everywhere and be done with it". As a storage class, it tells the compiler "don't reorder accesses to this variable".

Yah, disallowing code sharing is an intentional limitation of "shared". I'm glad that works :o). Andrei
Feb 12 2014
prev sibling next sibling parent "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Wednesday, 12 February 2014 at 16:25:35 UTC, Andrei 
Alexandrescu wrote:

 Yah, disallowing code sharing is an intentional limitation of 
 "shared". I'm glad that works :o).

Don't forget to emphasize this in second edition of TDPL :D
Feb 12 2014
prev sibling parent "tcak" <tcak pcak.com> writes:
On Wednesday, 12 February 2014 at 14:00:19 UTC, Stanislav Blinov 
wrote:
 To be concise: don't put all your eggs into one basket. If you 
 want shared class - write shared class.

 Is there really need to allow shared objects different 
 methods, and non-shared objects different methods? Why don't 
 we separate implementation instead of methods according to 
 being shared?

Exactly. Separate implementations. Completely. Shared is not just about "insert synchronized everywhere and be done with it". As a storage class, it tells the compiler "don't reorder accesses to this variable". Yes, as method attribute, it disallows you to call this method on an unshared reference. But that doesn't mean you now should rush to create both shared and non-shared methods for your classes: you'll be doing yourself a disservice. Design and implementation of concurrent data structures is very different from non-concurrent ones. Look up any concurrent data structure and see how drastically it differs from the plain, "single-threaded" one, even if it's built on lock-based synchronization, let alone lock-free. Providing two different implementations (that require different data for e.g. bookkeeping) inside one class is impractical and error-prone. If you're just going to synchronize every single method call for your class on a mutex, then you don't really need shared qualifier at all (well, you'll need to briefly cast the reference to shared to e.g. pass it to another thread). Also, I assume you understand that 'shared' and all its aspects are not yet fully realized in the language. Andrei mentioned that finalizing 'shared' should be made one of the primary goals. Maybe we would see some interesting syntactic solutions, type system improvements, etc. I think we already can help with some things (see e.g. my thread about shared sync primitives). But regardless, design your single-threaded and concurrent data separately, go easy on yourself :)

The thing is I have written many threaded applications in the last 15 years. Delphi, C, C#, Java. Lastly I started writing a webserver and web app framework in December 2012 in D2. It works at the same performance of Apache and never seen any problem yet, and still works. Though problem is that while D is preventing many mistakes in code with this shared keyword, it tired me more than other languages, and I just wanted share my thoughts about it.
Feb 12 2014