Thread overview
What classification should shared objects in qeued thread pools have?
Sep 30
IGotD-
Oct 01
mw
Oct 01
mw
Oct 01
IGotD-
Oct 01
mw
Oct 01
IGotD-
September 30
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?


October 01
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.
October 01
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
October 01
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.
October 01
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.
October 01
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.

October 01
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?