On Thursday, 18 October 2018 at 16:31:02 UTC, Stanislav Blinov wrote:
You contradict yourself and don't even notice it. Per your rules, the way to open that locked box is have shared methods that access data via casting. Also per your rules, there is absolutely no way for the programmer to control whether they're actually sharing the data. Therefore, some API can steal a shared reference without your approval, use that with your "safe" shared methods, while you're continuing to threat your data as not shared.

Yes, and that's fine. Because it's thread-safe, remember?


You and Manu both seem to think that methods allow you to "define a thread-safe interface".

struct S {
    void foo() shared;
}

Per your rules, S.foo is thread-safe. It is here that I remind you, *again*, what S.foo actually looks like, given made-up easy-to-read mangling:

void struct_S_foo(ref shared S);

And yet, for some reason, you think that these are not thread-safe:

void foo(shared int*);
void bar(ref shared int);

Again, no. No. No, no, no, no, no. We have not said that, we are not saying that, and we will not say that. Because it's not true, and I pointed out exactly this in a previous post. I have no idea where you got this idea, and I hope we can excise it. There is absolutely nothing wrong with void foo(shared int*). (apart from the fact it can't safely do anything)

For clarity: the interface of a type is any method, function, delegate or otherwise that may affect its internals. That means any free function in the same module, and any non-private members.


Your implementation of 'twaddle' is *unsafe*, because the compiler doesn't know that 'payload' is shared. For example, when inlining, it may reorder the calls in it and cause races or other UB. At least one of the reasons behind `shared` *was* to serve as compiler barrier.

Ah, now this is a good point - thanks! That does seem like it's a harder problem than has come up thus far.


Alright, so I have this shared object that I can't read from, and can't write to. It has no public shared members. What can I do with it? I can pass it to other guys, who also can't do anything with it. Are there other options?

It can have any number of public shared "members" per UFCS. The fact that you forget is that there's no difference between a method and a free function, other than syntax sugar. Well, OK, there's guaranteed private access for methods, but same is true for module members.

This again? See point 2, above. I hope we can stop this silliness soon.


The rest just follows naturally.

Nothing follows naturally. The proposal doesn't talk at all about the fact that you can't have "methods" on primitives,

You can't have thread-safe methods operating directly on primitives, because they already present a non-thread-safe interface. This is true. This follows naturally from the rules.

Everything in D already presents a non-threadsafe interface. Things that you advocate included.

struct S {
    void foo() shared;
}

That is not threadsafe. This *sort of* is:

struct S {
    @disable this(this);
    @disable void opAssign(S);

    void foo() shared;
}

...except not quite still. Now, if the compiler generated above in the presence of any `shared` members or methods, then we could begin talking about it being threadsafe. But that part is mysteriously missing from Manu's proposal, even though I keep reminding of this in what feels like every third post or so (I'm probably grossly exaggerating).

Again, this is good stuff. This is an actual example of what can go wrong. Thanks!


that you can't distinguish between shared and unshared data if that proposal is realized,

And you can't do that currently either. Just like today, shared(T) means the T may or may not be shared with other thread. Nothing more, nothing less.

I don't think it means what you think it means. "May or may not be shared with other thread" means "you MUST treat it as if it's shared with other thread". That's it.

Yup, hence 'shared' on a method meaning 'thread-safe'. So it's fine. I think this has been mentioned before.


That's why automatic conversion doesn't make *any* sense, and that's why compiler error on attempting to pass over mutable as shared makes *perfect* sense.

No. Because shared access is thread-safe.


that you absolutely destroy D's TLS-by-default treatment...
I'm unsure what you mean by this.

You lose the ability to distinguish thread-local and shared data.

And when is this a problem? Again, anything that has shared access to something is incapable of doing anything non-thread-safe to it.


Functions that you must not be allowed to write per this same proposal. How quaint.

What? Which functions can't I write?

Uh-huh, only due to some weird convention that "methods" are somehow safer than free functions. Which they're not.

No. Again, point 2. Nobody says this.


Yup, this is correct. But wrap it in a struct, like e.g. Atomic!int, and everything's hunky-dory.

So again,

void atomicInc(shared int*); // is "not safe", but
void struct_Atomic_int_opUnary_plus_plus(ref shared Atomic); // is "safe"

No, void atomicInc(shared int*) is perfectly safe, as long as it doesn't cast away shared. Again, the problem is int already has a non-thread-safe interface, which Atomic!int doesn't. And once more, for clarity, this interface includes any function that has access to its private members, free function, method, delegate return value from a function/method, what have you. Since D's unit of encapsulation is the module, this has to be the case. For int, the members of that interface include all operators. For pointers, it includes deref and pointer arithmetic. For arrays indexing, slicing, access to .ptr, etc. None of these lists are necessarily complete.


I have no idea where I or Manu have said you can't make functions that take shared(T)*.

Because you keep saying they're unsafe and that you should wrap them up in a struct for no other reason than just "because methods are kosher".

Again, point 2. I think we have been remiss in the explanation of what we consider the interface.


Let's say it together: for a type to be thread-safe, all of its public members must be written in a thread-safe way.

It's shared private parts also must be written in a thread-safe way. Yes, they're private, but they still may be shared. Welcome to the communism of multithreading.

For the public members to be thread-safe, yes, the private parts must be thread-safe. That's always the case. If I'm going to build a skyscraper, I will not make the foundation out of cardboard.


Now, Two very good points came up in this post, and I think it's worth stating them again, because they do present possible issues with MP:

1) How does MP deal with reorderings in non-shared methods?

I don't know. I'd hide behind 'that's for the type implementor to handle', but it's a subtle enough problem that I'm not happy with that answer.


2) What about default members like opAssign and postblit?

The obvious solution is for the compiler to not generate these when a type has a shared method or is taken as shared by a free function in the same module. I don't like the latter part of that, but it should work.

--
  Simen

Reply via email to