Thread overview
synchronized classes and shared
Aug 17, 2022
Loara
Aug 18, 2022
frame
Aug 19, 2022
Loara
Aug 19, 2022
frame
Aug 19, 2022
Loara
Aug 20, 2022
Tejas
Aug 20, 2022
frame
August 17, 2022

When dealing with shared variables according to the official documentation only atomic operations are allowed on them. But since synchronized classes can be accessed only by one thread at time it's reasonable to allow accessing its members even inside shared context, indeed the compiler allows it at least for small codes like the following:

synchronized class A{
  private:
    int a;
  public:
    this() {
      a = 1;
    }

    int getA() pure{
      return a;
    }
}

int main(){
  shared A c = new A();
  writeln(c.getA());
  return 0;
}

So I ask why was this feature not added in the official documentation?

August 18, 2022
On Wednesday, 17 August 2022 at 21:49:11 UTC, Loara wrote:
> When dealing with `shared` variables according to the official documentation only atomic operations are allowed on them. But since `synchronized` classes can be accessed only by one thread at time it's reasonable to allow accessing its members even inside `shared` context, indeed the compiler allows it at least for small codes like the following:

It does not - which compiler version are you using?

Error: `shared` method `app.A.this` is not callable using a non-shared object

And it shouldn't - shared (atomic lock free) and synchronized (locking) are different concepts.
August 19, 2022

On Thursday, 18 August 2022 at 11:18:18 UTC, frame wrote:

>

It does not - which compiler version are you using?

Error: shared method app.A.this is not callable using a non-shared object

v2.100.0

>

And it shouldn't - shared (atomic lock free) and synchronized (locking) are different concepts.

shared means that data can be accessed by different threads, indeed when you want to send a pointer to another thread the pointed data must be shared even if it's a synchronized class. For example the following code

import std.stdio;
import std.concurrency;

synchronized class A{
  private:
    int a;
  public:
    this() pure {
      a = 1;
    }

    int getA() pure{
      return a;
    }
}

void thread(A a){
  writeln(a.getA());
}

int main(){
  auto c = new A();
  spawn(&thread, c);
  return 0;
}

won't compile

main.d(18): Error: `shared` method `main.A.getA` is not callable using a non-shared object
/usr/include/dlang/dmd/std/concurrency.d(519): Error: static assert:  "Aliases to mutable thread-local data not allowed."
main.d(23):        instantiated from here: `spawn!(void function(A), A)`

if you want to compile it your class must be shared

import std.stdio;
import std.concurrency;

synchronized class A{
  private:
    int a;
  public:
    this() pure {
      a = 1;
    }

    int getA() pure{
      return a;
    }
}

void thread(shared A a){
  writeln(a.getA());
}

int main(){
  auto c = new shared A();
  spawn(&thread, c);
  return 0;
}
August 19, 2022

On Friday, 19 August 2022 at 11:05:16 UTC, Loara wrote:

>

shared means that data can be accessed by different threads, indeed when you want to send a pointer to another thread the pointed data must be shared even if it's a synchronized class.

That is correct, however shared also has another meaning in concurrency in D. Consider that getA() would modify a then the compiler tells you to forget that and to use atomatic operations instead because shared is linked to them.

An atomic operation is a hardware instruction, synchronization is more or less software implemented. Very different things. So D says using shared is preparation for atomic operations that are considered more thread safe than any other mechanism and only allows such operations on objects that have that storage class, giving some guarantee if you are using it right (at least this is my understanding of it).

As synchronization needs a mutex, you would lose advantages of atomic and/or doing something wrong, so it makes no big sense to convert the code automatically or it could be an error too.

August 19, 2022

On Friday, 19 August 2022 at 13:49:00 UTC, frame wrote:

>

An atomic operation is a hardware instruction, synchronization is more or less software implemented. Very different things. So D says using shared is preparation for atomic operations that are considered more thread safe than any other mechanism and only allows such operations on objects that have that storage class, giving some guarantee if you are using it right (at least this is my understanding of it).

As synchronization needs a mutex, you would lose advantages of atomic and/or doing something wrong, so it makes no big sense to convert the code automatically or it could be an error too.

Right, but then Phobos should allow to pass also synchronized classes between threads and not only shared data. Actually synchronized classes are almost useless since you can't pass through threads without using low-level functions or using cast.

August 20, 2022

On Friday, 19 August 2022 at 19:43:09 UTC, Loara wrote:

>

On Friday, 19 August 2022 at 13:49:00 UTC, frame wrote:

>

[...]

Right, but then Phobos should allow to pass also synchronized classes between threads and not only shared data. Actually synchronized classes are almost useless since you can't pass through threads without using low-level functions or using cast.

I believe Walter has already said that the synchronised stuff is a failed experiment and to just ignore it

August 20, 2022

On Friday, 19 August 2022 at 19:43:09 UTC, Loara wrote:

>

Right, but then Phobos should allow to pass also synchronized classes between threads and not only shared data. Actually synchronized classes are almost useless since you can't pass through threads without using low-level functions or using cast.

I would say there is no way to detect for a phobos module if the object passed would be a synchronized one but I totally missed out the fact that the synchronized class methods becomes indeed shared ones even if you don't explict declare it.

You can't just create the object without cast or explicit declaration, as A.__ctor() becomes shared:

A c      = new A();        // fails, no shared included
auto c   = new A();        // fails, shared is strictly required
shared c = new A();        // fails also, but I think it that one should work
shared c = new shared A(); // works
auto c   = new shared A(); // works, obviously

Conclusion:

You have to deal with the shared thing anyway.