Thread overview
[dmd-concurrency] Practical use of shared qualifier?
Mar 08, 2011
d coder
Mar 09, 2011
Robert Jacques
Mar 15, 2011
Sean Kelly
Mar 15, 2011
d coder
Mar 18, 2011
Sean Kelly
Apr 14, 2011
d coder
March 08, 2011
Greetings

I lately came across usefulness of "shared" qualifier in section 13.12.1. After reading the section, I guess I need to use shared qualifier more frequently than to just make sure that global variables are not treated thread-local. I think I need to make all class objects that would be accessed by multiple threads shared as well. Adding shared qualifier to these variables would make sure that the compiler adds memory barriers wherever applicable and that the compiler maintains sequential consistency for these objects. Am I right?

I tried to follow this on the application I am developing. Note that I am using threads from std.thread library (not MPI from std.concurrency). I started by adding shared qualifier on the variable declarations that I know would be accessed by multiple threads. To my surprise the compiler forces me to declare all the functions that take shared variables as parameters as synchronized. Since this applies to shared "this" objects as well, I am being forced to declare practically every method/function in my code as synchronous. While I understand that the compiler is trying to enforce such type checking to ensure mutex locking on shared objects, it is turning out to be pretty much impractical to use in my case. I would have preferred using synchronized code blocks for my critical sections instead of making all my functions synchronized.

Another issue is that the std container library does not seem to work with shared objects. So I have to do a lot of explicit casting while using this library with the objects that I have qualified as shared.

Kindly let me know if I am missing something. I tried google code search and
could not find anybody using much of shared qualifier. While I know the
number of shared objects has to be minimized by design, the nature of my
application is such that I am forced to use std.thread library and
am constrained to have lots of shared objects.

I am kind of stuck right now. Will appreciate any inputs.

Regards
- Puneet
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/dmd-concurrency/attachments/20110308/0fbd620b/attachment.html>
March 09, 2011
Hi Puneet,
This list was used to discuss the creation of D's concurrency model and is
basically dead at this point. I'd strongly suggest posting to the D.learn
or D newsgroups, instead. Regarding your specific point, yes, the compiler
should insert the correct memory barriers for shared variables. The issue
with regard to methods that you are seeing indicates that you are not
declaring your classes either as shared or synchronized; I wasn't aware it
was still possible to declare a non-shared class as shared. Anyways, the
proper way to declare a class is either:

shared A {}

or

synchronized B {}

which will cause all the methods of A/B to be declared as shared/synchronized, respectively. shared methods allow you to manually control synchronization blocks and/or do lock-free programming, etc. The container library is still under development and doesn't contain concurrent containers yet. (implementations are welcome)


On Mon, 07 Mar 2011 20:19:42 -0500, d coder <dlang.coder at gmail.com> wrote:

> Greetings
>
> I lately came across usefulness of "shared" qualifier in section 13.12.1.
> After reading the section, I guess I need to use shared qualifier more
> frequently than to just make sure that global variables are not treated
> thread-local. I think I need to make all class objects that would be
> accessed by multiple threads shared as well. Adding shared qualifier to
> these variables would make sure that the compiler adds memory barriers
> wherever applicable and that the compiler maintains sequential
> consistency
> for these objects. Am I right?
>
> I tried to follow this on the application I am developing. Note that I am
> using threads from std.thread library (not MPI from std.concurrency). I
> started by adding shared qualifier on the variable declarations that I
> know
> would be accessed by multiple threads. To my surprise the compiler
> forces me
> to declare all the functions that take shared variables as parameters as
> synchronized. Since this applies to shared "this" objects as well, I am
> being forced to declare practically every method/function in my code as
> synchronous. While I understand that the compiler is trying to enforce
> such
> type checking to ensure mutex locking on shared objects, it is turning
> out
> to be pretty much impractical to use in my case. I would have preferred
> using synchronized code blocks for my critical sections instead of making
> all my functions synchronized.
>
> Another issue is that the std container library does not seem to work
> with
> shared objects. So I have to do a lot of explicit casting while using
> this
> library with the objects that I have qualified as shared.
>
> Kindly let me know if I am missing something. I tried google code search
> and
> could not find anybody using much of shared qualifier. While I know the
> number of shared objects has to be minimized by design, the nature of my
> application is such that I am forced to use std.thread library and
> am constrained to have lots of shared objects.
>
> I am kind of stuck right now. Will appreciate any inputs.
>
> Regards
> - Puneet
March 15, 2011
The compiler should, but it doesn't yet. For the next release I'm going to expose atomicLoad and atomicStore to deal with this until shared is fully implemented.

Sent from my iPhone

On Mar 9, 2011, at 9:05 AM, "Robert Jacques" <sandford at jhu.edu> wrote:

> Hi Puneet,
> This list was used to discuss the creation of D's concurrency model and is basically dead at this point. I'd strongly suggest posting to the D.learn or D newsgroups, instead. Regarding your specific point, yes, the compiler should insert the correct memory barriers for shared variables. The issue with regard to methods that you are seeing indicates that you are not declaring your classes either as shared or synchronized; I wasn't aware it was still possible to declare a non-shared class as shared. Anyways, the proper way to declare a class is either:
> 
> shared A {}
> 
> or
> 
> synchronized B {}
> 
> which will cause all the methods of A/B to be declared as shared/synchronized, respectively. shared methods allow you to manually control synchronization blocks and/or do lock-free programming, etc. The container library is still under development and doesn't contain concurrent containers yet. (implementations are welcome)
> 
> 
> On Mon, 07 Mar 2011 20:19:42 -0500, d coder <dlang.coder at gmail.com> wrote:
> 
>> Greetings
>> 
>> I lately came across usefulness of "shared" qualifier in section 13.12.1. After reading the section, I guess I need to use shared qualifier more frequently than to just make sure that global variables are not treated thread-local. I think I need to make all class objects that would be accessed by multiple threads shared as well. Adding shared qualifier to these variables would make sure that the compiler adds memory barriers wherever applicable and that the compiler maintains sequential consistency for these objects. Am I right?
>> 
>> I tried to follow this on the application I am developing. Note that I am using threads from std.thread library (not MPI from std.concurrency). I started by adding shared qualifier on the variable declarations that I know would be accessed by multiple threads. To my surprise the compiler forces me to declare all the functions that take shared variables as parameters as synchronized. Since this applies to shared "this" objects as well, I am being forced to declare practically every method/function in my code as synchronous. While I understand that the compiler is trying to enforce such type checking to ensure mutex locking on shared objects, it is turning out to be pretty much impractical to use in my case. I would have preferred using synchronized code blocks for my critical sections instead of making all my functions synchronized.
>> 
>> Another issue is that the std container library does not seem to work with shared objects. So I have to do a lot of explicit casting while using this library with the objects that I have qualified as shared.
>> 
>> Kindly let me know if I am missing something. I tried google code search and
>> could not find anybody using much of shared qualifier. While I know the
>> number of shared objects has to be minimized by design, the nature of my
>> application is such that I am forced to use std.thread library and
>> am constrained to have lots of shared objects.
>> 
>> I am kind of stuck right now. Will appreciate any inputs.
>> 
>> Regards
>> - Puneet
> _______________________________________________
> dmd-concurrency mailing list
> dmd-concurrency at puremagic.com
> http://lists.puremagic.com/mailman/listinfo/dmd-concurrency
March 15, 2011
>
>
> The compiler should, but it doesn't yet. For the next release I'm going to expose atomicLoad and atomicStore to deal with this until shared is fully implemented.


Thanks Sean

I have read some literature which says that shared variables should be declared volatile (in C++). But most people say that having mutex locks is sufficient and making variables volatile just adds to inefficiency.

Now I am not an expert in concurrency. I want to ask you this. Is it not sufficient just to mark critical code segments as synchronized? This is how things are in Java and with pthread library.

In my application, when I use shared variables (and since shared is transitive) I end up making most of my code synchronized. On top of that there is no way (that I know of) to use another lock/monitor than the class object itself with synchronized class methods (AFAIK this can be done with only synchronized code blocks). In my case being forced to use the class object as monitor/lock for all class methods is giving rise to lots of deadlocks.

So I am having a doubt on the practical aspect of shared variables. Would it not be nice to leave it to the users to decide which part of the code to declare synchronized -- and also leave the choice of lock/monitor too to the coder? Presently I see that synchronized methods have to be bound to the class object as a monitor and all the methods having a shared function argument must be declared synchronized method.

Thanks and Regards
- Puneet
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/dmd-concurrency/attachments/20110315/f29f7480/attachment.html>
March 18, 2011
On Mar 15, 2011, at 10:43 AM, d coder wrote:

> I have read some literature which says that shared variables should be declared volatile (in C++). But most people say that having mutex locks is sufficient and making variables volatile just adds to inefficiency.
> 
> Now I am not an expert in concurrency. I want to ask you this. Is it not sufficient just to mark critical code segments as synchronized? This is how things are in Java and with pthread library.

Yes.  In fact, using synchronized should always be the default choice.  For the most part, entering a synchronized block can be assumed to require one volatile operation, and leaving the block may require a second volatile operation.  This is far more efficient that writing the same code using all volatile variables, for obvious reasons.

Another issue is that once execution of a shared code segment isn't synchronized, the algorithm itself will most likely have to change if it is to remain correct.  Consider inserting into a doubly linked-list.  You find the location and then update two list pointers to insert your node.  If all this is synchronized then the entire operation is atomic so you can write the code a million different ways and produce the same result.  If all the involved variables are volatile then the operations are "seen" by other threads in program order, but consider the case where two threads try inserting nodes into the same location:

class Node {}

class List(T) {
    shared void insert(shared Node insertPos, shared Node newNode) {
        newNode.prev = insertPos.prev;
        newNode.next = insertPos;

        insertPos.prev.next = newNode; // 1
        insertPos.prev = newNode; // 2
    }
}

Let's say that thread A and thread B are both inserting into the same insertPos.  Thread A executes line 1 and then stalls.  Thread B executes lines 1 and 2 and returns.  Then thread A resumes and executes line 2.  Even though all the involved operations were volatile, some of the list pointers reference thread A's new node and some reference thread B's new node (ie. the list is All Screwed Up).  And you're looking at what amounts to at least 4 volatile operations: a load of insertPos.prev, a store to newNode.prev, a store to newNode.next, a load of insertPos.prev followed by a store to insertPos.prev.next, etc.  Even if the compiler were smart enough to eliminate all unnecessary stuff and the CPU were designed such that little is actually necessary to make the code work as the compiler intends (x86 is pretty close), this function is unlikely to execute in fewer cycles than an identical synchronized function.

> In my application, when I use shared variables (and since shared is transitive) I end up making most of my code synchronized. On top of that there is no way (that I know of) to use another lock/monitor than the class object itself with synchronized class methods (AFAIK this can be done with only synchronized code blocks). In my case being forced to use the class object as monitor/lock for all class methods is giving rise to lots of deadlocks.

The intention is for there to be very few shared variables in an application, otherwise you've gained nothing over an app written in say Java.

And for what it's worth, you can have multiple objects share a monitor: use core.sync.mutex.  I think it's currently missing a function to actually assign it to an existing class instance though.  Currently, it can only become an object's monitor on construction.  I'll have to change this for 2.053.

> So I am having a doubt on the practical aspect of shared variables. Would it not be nice to leave it to the users to decide which part of the code to declare synchronized -- and also leave the choice of lock/monitor too to the coder? Presently I see that synchronized methods have to be bound to the class object as a monitor and all the methods having a shared function argument must be declared synchronized method.

I think it would be nice if the user could declare only relevant sections of his code synchronized.  Typically, you want synchronized blocks to be as small as possible to improve concurrency, etc, which isn't supported by having to label entire functions as synchronized.  But I suspect that it's much more difficult for a compiler to verify code written like this.  If you'll notice, the application of 'shared' and 'synchronized' to class members/data is basically identical to how 'const' is applied.

Personally, I really like the visibility aspect of 'shared' (ie. statics are thread-local by default, global if shared), but am less fond of how it applies to class types.  I've been hoping that a typical app will only have a few shared objects, all being things like containers.  The "vast web of shared objects" approach, as in Java, is an invitation to disaster.


April 14, 2011
>
> The compiler should, but it doesn't yet. For the next release I'm going to expose atomicLoad and atomicStore to deal with this until shared is fully implemented.


Sean

I hope you checkin the changes in time for the next release. I am depending on these changes.

BTW where is atomicStore defined? I do not see it in the core.atomic module.

Regards
- Puneet
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/dmd-concurrency/attachments/20110414/d91967fe/attachment.html>