July 14, 2020
---
import std;

shared class TimeCount {
	synchronized void startClock() {
		auto me = cast()this;
		me.startTime = Clock.currTime;
	}
	synchronized void endClock() {
		auto me = cast()this;
		me.endTime = Clock.currTime;
	}
	synchronized void calculateDuration() {
		auto me = cast()this;
		me.elapsed = me.endTime - me.startTime;
	}

	private:
	SysTime startTime;
	SysTime endTime;
	Duration elapsed;
}
---
forgot synchronized attribute
July 14, 2020
On 14/7/20 8:05, Kagamin wrote:
> On Monday, 13 July 2020 at 07:26:06 UTC, Arafel wrote:
>> That's exactly why what I propose is a way to *explicitly* tell the compiler about it, like @system does for safety.
> 
> With __gshared you can opt out from sharing safety, then you're back to old good C-style multithreading.

That's apples and oranges.

I do agree in principle with the idea of `shared`, I just want a way to tell the compiler that `shared` doesn't apply *within a given block*, if possible also only for some specific variables, because I have already taken care of the synchronization, that's exactly what the system tries to promote.

__gshared on the other hand is just dispensing with the `shared` system altogether and giving up the protections it offers. Furthermore it only works for global objects and static variables/members [1], so its use is limited.

[1]: https://dlang.org/spec/attribute.html#gshared
July 14, 2020
On 14/7/20 8:13, Kagamin wrote:
> ---
> import std;
> 
> shared class TimeCount {
>      void startClock() {
>          auto me = cast()this;
>          me.startTime = Clock.currTime;
>      }
>      void endClock() {
>          auto me = cast()this;
>          me.endTime = Clock.currTime;
>      }
>      void calculateDuration() {
>          auto me = cast()this;
>          me.elapsed = me.endTime - me.startTime;
>      }
> 
>      private:
>      SysTime startTime;
>      SysTime endTime;
>      Duration elapsed;
> }
> ---
> And this is shorter than your unshared member specification.

It won't work if you need to do it inside a struct instead of a class, because you'll get a copy:

```
import std;

shared struct S {
    void setA (int _a) {
        auto me = cast() this;
        me.a = _a;
    }
    int a;
}

void main() {
    shared S s;
    writeln("Before: ", s.a); // 0
    s.setA(42);
    writeln("After: ", s.a); // still 0
}
```

That said, `with (cast() this) { ... }` *will* work, because there's no copying. This is a really nice idiom that I didn't know and that I'll use from now on.

*However*, for this to work, you shouldn't use `shared` member variables unless absolutely necessary, much less whole `shared` classes/structs, and only declare the individual methods as shared, because casting away `shared` from `this` will only peel the external layer:

```
import std;

struct S {
    SysTime a;
    shared SysTime b;

    synchronized shared void setIt(SysTime t) {
        with(cast() this) {
            a = t;
            // b = t; // FAILS, `b` is still `shared` even for non-shared `S`
        }
    }
}
```

Also, I'm pretty sure there are still corner cases when you have to nest data structures, but so far this strategy seems good enough.
July 14, 2020
On Tuesday, 14 July 2020 at 07:05:43 UTC, Arafel wrote:
> *However*, for this to work, you shouldn't use `shared` member variables unless absolutely necessary, much less whole `shared` classes/structs

This is generally true. Avoid sharing many variables!
Tasks should be as independent from each other as possible. Anything else is bad design doomed to run into problems sooner or later.
Also there is really almost never a good reason to share whole classes or nested structures.
July 14, 2020
On 14/7/20 10:45, Dominikus Dittes Scherkl wrote:
> 
> This is generally true. Avoid sharing many variables!
> Tasks should be as independent from each other as possible. Anything else is bad design doomed to run into problems sooner or later.
> Also there is really almost never a good reason to share whole classes or nested structures.

Sometimes you want to encapsulate your "shared" logic.

For instance, a class (or struct) might be a thread-safe container responsible for storing shared data across multiple threads. Each thread would get a shared reference to the container, and all the synchronization would be managed internally by that class.

In these cases I just marked the whole container class as `shared` instead of having to mark every single method, in fact there were no non-shared methods at all.

Now I know that the members shouldn't be shared, just the methods, but the difference wasn't clear to me until now, because it only shows when you cast shared away from `this`.

If you only instantiate shared variables, all the members become automatically shared as well, even if they originally weren't. So far I was just removing shared from the individual members, so I didn't notice:

```
import std;

class S {
    SysTime a;
    shared SysTime b;

    synchronized shared void setIt(SysTime t) {
        // What I used to do
        cast () a = t; // Here you need to cast away shared anyway
        cast () b = t; // so it doesn't make any difference.

        // What I'll do from now on
        with(cast() this) { // You only notice the difference when you cast away `shared` from `this`
            a = t;
            // b = t; // FAILS
        }
    }
}
```
July 14, 2020
Yes, all the synchronization and casting pretty much mandates that shared data must be behind some kind of abstraction for better ergonomics and better correctness too.
1 2 3
Next ›   Last »