www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - What classification should shared objects in qeued thread pools have?

reply IGotD- <nise nise.com> writes:
I have a system that heavily relies on thread pools. Typically 
this is used with items that are put on a queue and then a thread 
pool system process this queue. The thread pool can be configured 
to process the items in whatever parallel fashion it wants but 
usually it is set to one, that means no special protection for 
concurrency is needed as serialization is guaranteed. One item is 
processed at a time.

So think that one item is being processed and then put on some 
kind of dynamic data structure. During this operation allocations 
and deallocations can happen. Because we have a thread pool, 
these memory operations can happen in different threads.

This where the memory model of D starts to become confusing for 
me. By default memory allocations/deallocation are not allowed 
between threads, however setting the object to shared circumvents 
this. This seems to work as there is no more aborts from the D 
memory management. However, this has a weird side effect. Now the 
compiler wants that all my integer member variables operates by 
using atomic primitives. I don't need this, I know that this 
object will be sequentially used.

Is shared the wrong way to go here and is there another way to 
solve this?
Sep 30 2020
parent reply mw <mingwu gmail.com> writes:
On Wednesday, 30 September 2020 at 20:13:47 UTC, IGotD- wrote:
 This where the memory model of D starts to become confusing for 
 me. By default memory allocations/deallocation are not allowed 
 between threads, however setting the object to shared 
 circumvents this. This seems to work as there is no more aborts 
 from the D memory management. However, this has a weird side
I think declaring the container and item as `shared` is the D's encouraged way of sharing data among different threads.
 effect. Now the compiler wants that all my integer member 
 variables operates by using atomic primitives. I don't need 
 this, I know that this object will be sequentially used.
then just cast the `shared` away: cast()data.
 Is shared the wrong way to go here and is there another way to 
 solve this?
I think using `shared` is the D's encouraged way. If there is a better way do this in D, I'd want to know it too.
Sep 30 2020
next sibling parent mw <mingwu gmail.com> writes:
On Thursday, 1 October 2020 at 00:00:06 UTC, mw wrote:
 On Wednesday, 30 September 2020 at 20:13:47 UTC, IGotD- wrote:
 [...]
I think declaring the container and item as `shared` is the D's encouraged way of sharing data among different threads.
 [...]
then just cast the `shared` away: cast()data.
 [...]
I think using `shared` is the D's encouraged way. If there is a better way do this in D, I'd want to know it too.
BTW, this is what I learnt from my previous thread: https://forum.dlang.org/post/nlhjzafnmywrgkyjljsi forum.dlang.org
Sep 30 2020
prev sibling parent reply IGotD- <nise nise.com> writes:
On Thursday, 1 October 2020 at 00:00:06 UTC, mw wrote:
 I think using `shared` is the D's encouraged way.

 If there is a better way do this in D, I'd want to know it too.
I think that the shared in shared structs should not be transitive to members of the struct. The compiler should not enforce this as we don't really know what the programmer will do inside the struct to ensure the thread safety. For example completely lockless algorithms can often be a combination of atomic operations and also non-atomic operations on data members. I originally thought that DIP 1024 only applied for integer types alone (not inside structs). I don't really understand the rationale why a shared struct should all have atomic integers, it doesn't make any sense.
Sep 30 2020
next sibling parent mw <mingwu gmail.com> writes:
On Thursday, 1 October 2020 at 00:13:41 UTC, IGotD- wrote:
 I think that the shared in shared structs should not be 
 transitive to members of the struct. The compiler should not
Once the aggregate struct data is decl-ed `shared` as a whole, it needs to be transitive to play safe. The compiler (technology) is not that advanced enough to either prove that for a particular sub-component of a shared struct data is fine without protection, or the programmer's intention.
 enforce this as we don't really know what the programmer will 
 do inside the struct to ensure the thread safety.
If the programmer is really sure about his/her design, just cast() the `shared` away.
Sep 30 2020
prev sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 1 October 2020 at 00:13:41 UTC, IGotD- wrote:
 For example completely lockless algorithms can often be a 
 combination of atomic operations and also non-atomic operations 
 on data members.
Also, atomic operations on members do not ensure the integrity of the struct. For that you need something more powerful (complicated static analysis or transactional memory). I'm very wary of being able to cast away shared, it might completely negate all the advertised (memory management) optimization opportunities for shared. For that to work you need some kind of "unshared" or "borrowed" like concept.
Oct 01 2020
parent IGotD- <nise nise.com> writes:
On Thursday, 1 October 2020 at 14:12:24 UTC, Ola Fosheim Grøstad 
wrote:
 Also, atomic operations on members do not ensure the integrity 
 of the struct. For that you need something more powerful 
 (complicated static analysis or transactional memory).

 I'm very wary of being able to cast away shared, it might 
 completely negate all the advertised (memory management) 
 optimization opportunities for shared.

 For that to work you need some kind of "unshared" or "borrowed" 
 like concept.
Making all variables atomic in a shared struct is a intelligent as putting all hands on 4:00 on an analogue alarm clock if you want to wake up at 4:00. Atomic operations in itself does not ensure thread safety, you can still have races and the lockless algorithm might not be waterproof. They can be very difficult to design. Sometimes, this can show up months after a product has gone live. Furthermore, there is also the possibility to use locking primitives (mutexes, read write locks) inside a shared struct to ensure the thread safety. In that case you really don't all the data member operations to be atomic. In order to have "everything allowed" struct like in C++, shouldn't __gshared also work so that the allocator can successfully do its operations from several threads?
Oct 01 2020