Re: Mallocator and 'shared'

2017-02-15 Thread Kagamin via Digitalmars-d-learn
On Tuesday, 14 February 2017 at 15:57:47 UTC, Moritz Maxeiner 
wrote:
You seem to be trying to argue against someone stating memory 
barriers should be emitted automatically, though I don't know 
why you think that's me; You initially stated that
Memory barriers are a bad idea because they don't defend from 
a race condition, but they look like they do


You were concerned that implementation doesn't match FAQ 
regarding automatic barriers. I thought we were talking about 
that.


Re: Mallocator and 'shared'

2017-02-14 Thread Kagamin via Digitalmars-d-learn

On Tuesday, 14 February 2017 at 10:52:37 UTC, Johannes Pfau wrote:
But if the interrupt accesses a variable and a normal function 
accesses the variable as well, the access needs to be 
'volatile' (not cached into a register by the compiler; not 
closely related to this discussion) and atomic, as the 
interrupt might occur in between multiple partial writes. So 
the variable should be shared, although there's no 
multithreading (in the usual sense).


Single publishing (loggers, singletons) doesn't need atomic load: 
https://forum.dlang.org/post/ozssiniwkaghcjvrl...@forum.dlang.org


Re: Mallocator and 'shared'

2017-02-14 Thread Moritz Maxeiner via Digitalmars-d-learn

On Tuesday, 14 February 2017 at 14:27:05 UTC, Kagamin wrote:
On Monday, 13 February 2017 at 17:44:10 UTC, Moritz Maxeiner 
wrote:
To be clear: While I might, in general, agree that using 
shared methods only for thread safe methods seems to be a 
sensible restriction, neither language nor compiler require it 
to be so; and absence of evidence of a useful application is 
not evidence of absence.


Right, a private shared method can be a good use case for a 
thread-unsafe shared method.



---
__gshared int f = 0, x = 0;
Object monitor;

// thread 1
synchronized (monitor) while (f == 0);
// Memory barrier required here
synchronized (monitor) writeln(x)

// thread 2
synchronized (monitor) x = 42;
// Memory barrier required here
synchronized (monitor) f = 1;
---


Not sure about this example, it demonstrates a deadlock.


That's beside the point, but I guess I should've clarified the 
"not needed" as "harmful". The point was that memory barriers and 
synchronization are two separate solutions for two separate 
problems and your post scriptum about memory barriers disregards 
that synchronization does not apply to the problem memory 
barriers solve.


On Tuesday, 14 February 2017 at 14:27:05 UTC, Kagamin wrote:


My opinion on the matter of `shared` emitting memory barriers 
is that either the spec and documentation[1] should be updated 
to reflect that sequential consistency is a non-goal of 
`shared` (and if that is decided this should be accompanied by 
an example of how to add memory barriers yourself), or it 
should be implemented.


I'm looking at this in terms of practical consequences and 
useful language features.


So am I.

On Tuesday, 14 February 2017 at 14:27:05 UTC, Kagamin wrote:
What people are supposed to think and do when they see 
"guarantees sequential consistency"? I mean people at large.


That's a documentation issue, however, and is imho not relevant 
to the decision whether one should, or should not emit memory 
barriers. It's only relevant to how the decision is then 
presented to the people at large.


On Tuesday, 14 February 2017 at 14:27:05 UTC, Kagamin wrote:


I agree, message passing is considerably less tricky and 
you're unlikely to shoot yourself in the foot. Nonetheless, 
there are valid use cases where the overhead of MP may not be 
acceptable.


Performance was a reason to not provide barriers. People, who 
are concerned with performance, are even unhappy with virtual 
methods, they won't be happy with barriers on every memory 
access.


You seem to be trying to argue against someone stating memory 
barriers should be emitted automatically, though I don't know why 
you think that's me; You initially stated that
Memory barriers are a bad idea because they don't defend from a 
race condition, but they look like they do
which I rebutted since memory barriers have nothing to do with 
race conditions. Whether memory barriers should automatically 
emitted by the compiler is a separate issue, one on which my 
position btw is that they shouldn't. The current documentation of 
`shared`, however, implies that such an emission (and the related 
sequential consistency) is a goal of `shared` (and just not - 
yet? - implemented) and does not reflect the apparently final 
decision that it's not.




Re: Mallocator and 'shared'

2017-02-14 Thread Johannes Pfau via Digitalmars-d-learn
Am Tue, 14 Feb 2017 14:38:32 +
schrieb Kagamin :

> On Tuesday, 14 February 2017 at 10:52:37 UTC, Johannes Pfau wrote:
> > I remember some discussions about this some years ago and IIRC 
> > the final decision was that the compiler will not magically 
> > insert any barriers for shared variables.  
> 
> It was so some years ago, not sure if it's still so. I suspect 
> automatic barriers come from TDPL book and have roughly the same 
> rationale as autodecoding. They fix something, guess if this 
> something is what you need.

At least this thread is from 2012, so more recent than TDPL:
http://forum.dlang.org/post/k7pn19$bre$1...@digitalmars.com

I'm not sure though if there were any further discussions/decisions
after that discussion.

-- Johannes



Re: Mallocator and 'shared'

2017-02-14 Thread Johannes Pfau via Digitalmars-d-learn
Am Tue, 14 Feb 2017 13:01:44 +
schrieb Moritz Maxeiner :
  
> 
> It's not supposed to. Also, your example does not implement the 
> same semantics as what I posted and yes, in your example, there's 
> no need for memory barriers. In the example I posted, 
> synchronization is not necessary, memory barriers are (and since 
> synchronization is likely to have a significantly higher runtime 
> cost than memory barriers, why would you want to, even if it were 
> possible).
> 

I'll probably have to look up about memory barriers again, I never
really understood when they are necessary ;-)


> >
> > I remember some discussions about this some years ago and IIRC 
> > the final decision was that the compiler will not magically 
> > insert any barriers for shared variables. Instead we have 
> > well-defined intrinsics in std.atomic dealing with this. Of 
> > course most of this stuff isn't implemented (no shared support 
> > in core.sync).
> >
> > -- Johannes  
> 
> Good to know, thanks, I seem to have missed that final decision. 
> If that was indeed the case, then that should be reflected in the 
> documentation of `shared` (including the FAQ).

https://github.com/dlang/dlang.org/pull/1570

I think it's probably somewhere in this thread:

http://forum.dlang.org/post/k7pn19$bre$1...@digitalmars.com

>1. Slapping shared on a type is never going to make algorithms on that
>type work in a concurrent context, regardless of what is done with
>memory barriers. Memory barriers ensure sequential consistency, they
>do nothing for race conditions that are sequentially consistent.
>Remember, single core CPUs are all sequentially consistent, and still
>have major concurrency problems. This also means that having templates
>accept shared(T) as arguments and have them magically generate correct
>concurrent code is a pipe dream.
>
>2. The idea of shared adding memory barriers for access is not going
>to ever work. Adding barriers has to be done by someone who knows what
>they're doing for that particular use case, and the compiler inserting
>them is not going to substitute.
>
>However, and this is a big however, having shared as compiler-enforced
>self-documentation is immensely useful. It flags where and when data
>is being shared.

http://forum.dlang.org/post/mailman.1904.1352922666.5162.digitalmar...@puremagic.com

> Most of the reason for this was that I didn't like the old
> implications of shared, which was that shared methods would at some
> time in the future end up with memory barriers all over the place.
> That's been dropped, [...]


-- Johannes



Re: Mallocator and 'shared'

2017-02-14 Thread Kagamin via Digitalmars-d-learn

On Tuesday, 14 February 2017 at 10:52:37 UTC, Johannes Pfau wrote:
I remember some discussions about this some years ago and IIRC 
the final decision was that the compiler will not magically 
insert any barriers for shared variables.


It was so some years ago, not sure if it's still so. I suspect 
automatic barriers come from TDPL book and have roughly the same 
rationale as autodecoding. They fix something, guess if this 
something is what you need.


Re: Mallocator and 'shared'

2017-02-14 Thread Kagamin via Digitalmars-d-learn
On Monday, 13 February 2017 at 17:44:10 UTC, Moritz Maxeiner 
wrote:
To be clear: While I might, in general, agree that using shared 
methods only for thread safe methods seems to be a sensible 
restriction, neither language nor compiler require it to be so; 
and absence of evidence of a useful application is not evidence 
of absence.


Right, a private shared method can be a good use case for a 
thread-unsafe shared method.



---
__gshared int f = 0, x = 0;
Object monitor;

// thread 1
synchronized (monitor) while (f == 0);
// Memory barrier required here
synchronized (monitor) writeln(x)

// thread 2
synchronized (monitor) x = 42;
// Memory barrier required here
synchronized (monitor) f = 1;
---


Not sure about this example, it demonstrates a deadlock.

My opinion on the matter of `shared` emitting memory barriers 
is that either the spec and documentation[1] should be updated 
to reflect that sequential consistency is a non-goal of 
`shared` (and if that is decided this should be accompanied by 
an example of how to add memory barriers yourself), or it 
should be implemented.


I'm looking at this in terms of practical consequences and useful 
language features. What people are supposed to think and do when 
they see "guarantees sequential consistency"? I mean people at 
large.


I agree, message passing is considerably less tricky and you're 
unlikely to shoot yourself in the foot. Nonetheless, there are 
valid use cases where the overhead of MP may not be acceptable.


Performance was a reason to not provide barriers. People, who are 
concerned with performance, are even unhappy with virtual 
methods, they won't be happy with barriers on every memory access.


Re: Mallocator and 'shared'

2017-02-14 Thread Moritz Maxeiner via Digitalmars-d-learn
On Tuesday, 14 February 2017 at 13:01:44 UTC, Moritz Maxeiner 
wrote:
Of course, I just wanted to point out that Kagamin's post 
scriptum is a simplification I cannot agree with. As a best 
practice? Sure. As a "never do it"? No.


Sorry for the double post, error in the above, please use this 
instead:


Of course, I just wanted to point out that Kagamin's
Thread unsafe methods shouldn't be marked shared, it doesn't 
make sense
is a simplification I cannot agree with. As a best practice? 
Sure. As a "never do it"? No.


Re: Mallocator and 'shared'

2017-02-14 Thread Moritz Maxeiner via Digitalmars-d-learn

On Tuesday, 14 February 2017 at 10:52:37 UTC, Johannes Pfau wrote:
The compiler of course can't require shared methods to be 
thread-safe as it simply can't prove thread-safety in all 
cases. This is like shared/trusted: You are supposed to make 
sure that a function behaves as expected. The compiler will 
catch some easy to detect mistakes (like calling a non-shared 
method from a shared method <=> system method from safe method) 
but you could always use casts, pointers, ... to fool the 
compiler.


You could use the same argument to mark any method as @trusted. 
Yes it's possible, but it's a very bad idea.


Though I do agree that there might be edge cases: In a single 
core, single threaded environment, should an interrupt function 
be marked as shared? Probably not, as no synchronization is 
required when calling the function.


But if the interrupt accesses a variable and a normal function 
accesses the variable as well, the access needs to be 
'volatile' (not cached into a register by the compiler; not 
closely related to this discussion) and atomic, as the 
interrupt might occur in between multiple partial writes. So 
the variable should be shared, although there's no 
multithreading (in the usual sense).


Of course, I just wanted to point out that Kagamin's post 
scriptum is a simplification I cannot agree with. As a best 
practice? Sure. As a "never do it"? No.


On Tuesday, 14 February 2017 at 10:52:37 UTC, Johannes Pfau wrote:

Am Mon, 13 Feb 2017 17:44:10 +
schrieb Moritz Maxeiner :
you'd still need those memory barriers. Also note that the 
synchronization in the above is not needed in terms of 
semantics.


However, if you move you synchronized to the complete sub-code 
blocks barriers are not necessary. Traditional mutex locking is 
basically a superset and is usually implemented using barriers 
AFAIK. I guess your point is we need to define whether shared 
methods guarantee some sort of sequential consistency?


My point in those paragraphs was that synchronization and memory 
barriers solve two different problems that can occur in 
non-sequential programming and because of Kagamin's statement


Memory barriers are a bad idea because they don't defend from a 
race condition
makes no sense (to me). But yes, I do think that the definition 
should have more background /context and not the current "D FAQ 
states `shared` guarantees sequential consistency (not 
implemented)". Considering how many years that has been the state 
I have personally concluded (for myself and how I deal with D) 
that sequential consistency is a non-goal of `shared`, but what's 
a person new to D supposed to think?


On Tuesday, 14 February 2017 at 10:52:37 UTC, Johannes Pfau wrote:


struct Foo
{
shared void doA() {lock{_tmp = "a";}};
shared void doB() {lock{_tmp = "b";}};
shared getA() {lock{return _tmp;}};
shared getB() {lock{return _tmp;}};
}

thread1:
foo.doB();

thread2:
foo.doA();
auto result = foo.getA(); // could return "b"

I'm not sure how a compiler could prevent such 'logic' bugs.


It's not supposed to. Also, your example does not implement the 
same semantics as what I posted and yes, in your example, there's 
no need for memory barriers. In the example I posted, 
synchronization is not necessary, memory barriers are (and since 
synchronization is likely to have a significantly higher runtime 
cost than memory barriers, why would you want to, even if it were 
possible).


On Tuesday, 14 February 2017 at 10:52:37 UTC, Johannes Pfau wrote:
However, I think it should be considered a best practice to 
always make a shared function a self-contained entity so that 
calling any other function in any order does not negatively 
effect the results. Though that might not always be possible.


Yes, that matches what I tried to express.

On Tuesday, 14 February 2017 at 10:52:37 UTC, Johannes Pfau wrote:

Am Mon, 13 Feb 2017 17:44:10 +
schrieb Moritz Maxeiner :
My opinion on the matter of `shared` emitting memory barriers 
is that either the spec and documentation[1] should be updated 
to reflect that sequential consistency is a non-goal of 
`shared` (and if that is decided this should be accompanied by 
an example of how to add memory barriers yourself), or it 
should be implemented. Though leaving it in the current "not 
implemented, no comment / plan on whether/when it will be 
implemented" state seems to have little practical consequence 
- since no one seems to actually work on this level in D - and 
I can thus understand why dealing with that is just not a 
priority.


I remember some discussions about this some years ago and IIRC 
the final decision was that the compiler will not magically 
insert any barriers for shared variables. Instead we have 
well-defined intrinsics in std.atomic dealing with this. Of 
course most of this stuff isn't implemented (no shared support 
in core.sync).


-- Johannes


Good to know, thanks, I seem to have missed 

Re: Mallocator and 'shared'

2017-02-14 Thread Johannes Pfau via Digitalmars-d-learn
Am Mon, 13 Feb 2017 17:44:10 +
schrieb Moritz Maxeiner :

> > Thread unsafe methods shouldn't be marked shared, it doesn't 
> > make sense. If you don't want to provide thread-safe interface, 
> > don't mark methods as shared, so they will not be callable on a 
> > shared instance and thus the user will be unable to use the 
> > shared object instance and hence will know the object is thread 
> > unsafe and needs manual synchronization.  
> 
> To be clear: While I might, in general, agree that using shared 
> methods only for thread safe methods seems to be a sensible 
> restriction, neither language nor compiler require it to be so; 
> and absence of evidence of a useful application is not evidence 
> of absence.

The compiler of course can't require shared methods to be thread-safe
as it simply can't prove thread-safety in all cases. This is like
shared/trusted: You are supposed to make sure that a function behaves
as expected. The compiler will catch some easy to detect mistakes (like
calling a non-shared method from a shared method <=> system method from
safe method) but you could always use casts, pointers, ... to fool the
compiler.

You could use the same argument to mark any method as @trusted. Yes
it's possible, but it's a very bad idea.

Though I do agree that there might be edge cases: In a single core,
single threaded environment, should an interrupt function be marked as
shared? Probably not, as no synchronization is required when calling
the function.

But if the interrupt accesses a variable and a normal function accesses
the variable as well, the access needs to be 'volatile' (not cached into
a register by the compiler; not closely related to this discussion) and
atomic, as the interrupt might occur in between multiple partial
writes. So the variable should be shared, although there's no
multithreading (in the usual sense).

> you'd still need those memory barriers. Also note that the 
> synchronization in the above is not needed in terms of semantics.

However, if you move you synchronized to the complete sub-code blocks
barriers are not necessary. Traditional mutex locking is basically a
superset and is usually implemented using barriers AFAIK. I guess your
point is we need to define whether shared methods guarantee some sort
of sequential consistency?

struct Foo
{
shared void doA() {lock{_tmp = "a";}};
shared void doB() {lock{_tmp = "b";}};
shared getA() {lock{return _tmp;}};
shared getB() {lock{return _tmp;}};
}

thread1:
foo.doB();

thread2:
foo.doA();
auto result = foo.getA(); // could return "b"

I'm not sure how a compiler could prevent such 'logic' bugs. However, I
think it should be considered a best practice to always make a shared
function a self-contained entity so that calling any other function in
any order does not negatively effect the results. Though that might not
always be possible.

> My opinion on the matter of `shared` emitting memory barriers is 
> that either the spec and documentation[1] should be updated to 
> reflect that sequential consistency is a non-goal of `shared` 
> (and if that is decided this should be accompanied by an example 
> of how to add memory barriers yourself), or it should be 
> implemented. Though leaving it in the current "not implemented, 
> no comment / plan on whether/when it will be implemented" state 
> seems to have little practical consequence - since no one seems 
> to actually work on this level in D - and I can thus understand 
> why dealing with that is just not a priority.

I remember some discussions about this some years ago and IIRC the
final decision was that the compiler will not magically insert any
barriers for shared variables. Instead we have well-defined intrinsics
in std.atomic dealing with this. Of course most of this stuff isn't
implemented (no shared support in core.sync).

-- Johannes



Re: Mallocator and 'shared'

2017-02-13 Thread Moritz Maxeiner via Digitalmars-d-learn

On Monday, 13 February 2017 at 14:20:05 UTC, Kagamin wrote:
Thread unsafe methods shouldn't be marked shared, it doesn't 
make sense. If you don't want to provide thread-safe interface, 
don't mark methods as shared, so they will not be callable on a 
shared instance and thus the user will be unable to use the 
shared object instance and hence will know the object is thread 
unsafe and needs manual synchronization.


To be clear: While I might, in general, agree that using shared 
methods only for thread safe methods seems to be a sensible 
restriction, neither language nor compiler require it to be so; 
and absence of evidence of a useful application is not evidence 
of absence.


On Monday, 13 February 2017 at 14:20:05 UTC, Kagamin wrote:
ps Memory barriers are a bad idea because they don't defend 
from a race condition, but they look like they do :)


There are two very common pitfalls in non-sequential programming 
with regards to reads/writes to memory shared between threads:
Issue 1: Sequencing/Interleaving of several threads into the 
logical memory access order

Issue 2: Reordering of code within one thread

Code that changes semantics because of issue 1 has race 
conditions; fixing it requires synchronization primitives, such 
as locking opcode, transactional memory, etc.


Code that changes semantics because of issue 2 may or may not 
have race conditions, but it definitely requires memory barriers.


Claiming that memory barriers are a bad idea because they don't 
defend against race conditions, but look like they do (when 
that's what synchronization is for) is similar enough to saying 
airbags in cars are a bad idea because they don't keep your body 
in place, but look like they do (when that's what seat belts are 
for).
My point here being that I don't understand what made you state 
that memory barriers look like they deal with race conditions, as 
they have nothing to do with that.


To be clear: Synchronization (the fix for race conditions) does 
not help you to deal with issue 2. If my last example had instead 
been


---
__gshared int f = 0, x = 0;
Object monitor;

// thread 1
synchronized (monitor) while (f == 0);
// Memory barrier required here
synchronized (monitor) writeln(x)

// thread 2
synchronized (monitor) x = 42;
// Memory barrier required here
synchronized (monitor) f = 1;
---

you'd still need those memory barriers. Also note that the 
synchronization in the above is not needed in terms of semantics. 
The code has no race conditions, all permutations of the 
(interleaved) memory access order yield the same output from 
thread 1. Also, since synchronization primitives and memory 
barriers have different runtime costs, depending on your hardware 
support and how they are translated to that support from D, 
there's no "one size fits all" solution on the low level we're on 
here.


My opinion on the matter of `shared` emitting memory barriers is 
that either the spec and documentation[1] should be updated to 
reflect that sequential consistency is a non-goal of `shared` 
(and if that is decided this should be accompanied by an example 
of how to add memory barriers yourself), or it should be 
implemented. Though leaving it in the current "not implemented, 
no comment / plan on whether/when it will be implemented" state 
seems to have little practical consequence - since no one seems 
to actually work on this level in D - and I can thus understand 
why dealing with that is just not a priority.


On Monday, 13 February 2017 at 14:20:05 UTC, Kagamin wrote:
use std.concurrency for a simple and safe concurrency, that's 
what it's made for.


I agree, message passing is considerably less tricky and you're 
unlikely to shoot yourself in the foot. Nonetheless, there are 
valid use cases where the overhead of MP may not be acceptable.


[1] https://dlang.org/faq.html#shared_guarantees


Re: Mallocator and 'shared'

2017-02-13 Thread Kagamin via Digitalmars-d-learn

On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
Given that both the data and the method are 'shared', a caller 
should know that race conditions are possible and that they 
should aquire a lock before accessing either of them...or so it 
would seem.


Thread unsafe methods shouldn't be marked shared, it doesn't make 
sense. If you don't want to provide thread-safe interface, don't 
mark methods as shared, so they will not be callable on a shared 
instance and thus the user will be unable to use the shared 
object instance and hence will know the object is thread unsafe 
and needs manual synchronization.


ps Memory barriers are a bad idea because they don't defend from 
a race condition, but they look like they do :) use 
std.concurrency for a simple and safe concurrency, that's what 
it's made for.


Re: Mallocator and 'shared'

2017-02-12 Thread Moritz Maxeiner via Digitalmars-d-learn

On Monday, 13 February 2017 at 01:30:57 UTC, ag0aep6g wrote:
This doesn't make sense to me. b depends on a. If I run thread 
1 alone, I can expect b to be 1, no?  Thread 2 can then a) read 
0, write 1; or b) read 1, write 2. How can b be 0 when the 
writeln is executed?


An example like this makes more sense to me:


shared int a = 0, b = 0;

// Thread 1:
a = 1;
b = 2;

// Thread 2:
writeln(a + b);


One might think that thread 2 cannot print "2", because from 
the order of statements the numbers must be 0 + 0 or 1 + 0 or 1 
+ 2. But thread 1 might execute `b = 2` before `a = 1`, because 
the order doesn't matter to thread 1. So 0 + 2 is possible, too.


You're right, of course, and I shall do well to remember not to 
think up examples for non-sequential code at such an hour, I am 
sorry. Thank you for providing a correct example plus 
explanation. The rest of my post still stands, though.
In recompense I shall provide another example, this one 
translated from Wikipedia[1] instead:


__gshared int f = 0, x = 0;

// thread 1
while (f == 0);
// Memory barrier required here
writeln(x)

// thread 2
x = 42;
// Memory barrier required here
f = 1;

The above demonstrates a case where you do need a memory barrier:
thread 1 and 2 have a consumer/producer relationship, where 
thread 1 wants to consume a value from thread 2 via `x`, using 
`f` to be notified that `x` is ready to be consumed;
Without memory barriers at both of the indicated lines the cpu is 
free to reorder either thread:
The first is required so that thread 1 doesn't get reordered to 
consume before being notified and the second so that thread 2 
doesn't get reordered to signal thread 1 before having produced.


If we had made `f` and `x` `shared` instead of `__gshared` the 
spec would require (at least) the two indicated memory barriers 
being emitted. Currently, though, it won't and for this case 
(AFAIK) `shared` won't get you any benefit I'm aware of over 
`__gshared`. You'll still need to add those memory barriers 
(probably using inline assembler, though I'm not sure what's the 
best way is in D, since I usually just stick with message 
passing).


[1] https://en.wikipedia.org/wiki/Memory_barrier


Re: Mallocator and 'shared'

2017-02-12 Thread ag0aep6g via Digitalmars-d-learn

On 02/13/2017 01:27 AM, Moritz Maxeiner wrote:

__gshared int a = 0;

// thread 1:
a = 1;
int b = a;
writeln(b);

// thread 2:
a += 1;

In the above, you may expect `b` to be either 1, or 2, depending on how
the cpu interleaves the memory access, but it can, in fact, also be 0,
since neither the compiler, nor the cpu can detect any reason as to why
`a = 1` should need to come before `int b = a` and may thus reorder the
write and the read.


This doesn't make sense to me. b depends on a. If I run thread 1 alone, 
I can expect b to be 1, no?  Thread 2 can then a) read 0, write 1; or b) 
read 1, write 2. How can b be 0 when the writeln is executed?


An example like this makes more sense to me:


shared int a = 0, b = 0;

// Thread 1:
a = 1;
b = 2;

// Thread 2:
writeln(a + b);


One might think that thread 2 cannot print "2", because from the order 
of statements the numbers must be 0 + 0 or 1 + 0 or 1 + 2. But thread 1 
might execute `b = 2` before `a = 1`, because the order doesn't matter 
to thread 1. So 0 + 2 is possible, too.


Re: Mallocator and 'shared'

2017-02-12 Thread ag0aep6g via Digitalmars-d-learn

On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
It seems like you're saying that 'shared' should mean both 
'thread safe' and 'not thread safe' depending on context, which 
doesn't make sense.


Makes sense to me: A `shared` variable is shared among threads. 
Accesses are not thread-safe. When a function has a `shared` 
parameter, it's expected to access that variable in a thread-safe 
manner. A `shared` method is a function with a `shared` `this` 
parameter.


Considering the alternative, that functions are not expected to 
ensure thread-safety on their `shared` parameters, that would 
mean you have to ensure it at the call site. And then what would 
be the point of marking the parameter `shared`, if thread-safety 
is already ensured?



Example:

shared A a;

struct A {
int x, y;

void foo() shared {
a.x = 1;
}
}

int main(string[] argv) {
a.x = 5;
a.y = 5;
a.foo();
return 0;
}

Qualifying 'a' with 'shared' means that it's shared between 
threads, which means that accessing it is _not_ thread safe.


Yup. In my opinion, non-atomic accesses like that should be 
rejected by the compiler. But you can write race conditions with 
only atomic stores/loads, so in the end it's the programmer's 
responsibility to write correct code.


Since the method 'foo' accesses 'a', 'foo' is also _not_ thread 
safe.


Well, yes, that `foo` isn't thread-safe. But it should be written 
differently so that it is.


Given that both the data and the method are 'shared', a caller 
should know that race conditions are possible and that they 
should aquire a lock before accessing either of them...or so it 
would seem.


But when you have the lock, you can safely call any method, 
including non-`shared` ones. I see no point in distinguishing 
`shared` and unshared methods then.


Non-`shared` methods are obviously not safe to call on `shared` 
objects. So `shared` methods must be other thing: safe.


I imagine that qualifying a method with 'shared' should mean 
that it can access shared data, and hence, is _not_ thread safe.


Every function/method can access shared data. They're all 
expected to do it safely. The `shared` attribute just qualifies 
the `this` reference.


This prevent access to 'shared' data from any non 'shared' 
context, without some kind of bridge/cast that a programmer 
would use when they knew that they had aquired the lock or 
ownership of the data. Although this is what would make sense 
to me, it doesn't seem to match with the current implementation 
of 'shared', or what you're saying.


It wouldn't exactly "prevent" it, would it? The compiler can't 
check that you've got the correct lock. It would be expected of 
the programmer to do so before calling the `shared` method. 
That's easy to get wrong.


When `shared` methods are safe themselves, you can't get the 
calls to them wrong. The ugly is nicely contained. To call an 
unsafe method, you have to cast and that's a good indicator that 
you're entering the danger zone.


It seems that methods qualified with 'shared' may be what 
you're suggesting matches up with the 'bridge' I'm trying to 
describe, but again, using the word 'shared' to mean both 
'thread safe' and 'not thread safe' doesn't make sense.


Maybe don't think of it meaning "safe" or "unsafe" then. It just 
means "shared".


A `shared` variable is just that: shared. The way you deal with 
it can be thread-safe or not. Everyone is expected to deal with 
it safely, though. "Everyone" includes `shared` methods.


Firstly, because the same keyword should not mean two strictly 
opposite things. Also, if a 'shared' method is supposed to be 
thread safe, then the fact that it has access to shared data is 
irrelevant to the caller.


Non-`shared` methods are not thread-safe. They expect unshared 
data. You can still call them on shared objects, though, with a 
cast. And when you've ensured thread-safety beforehand, it's 
perfectly fine to do so.


If `shared` methods were unsafe too, then that would only allow 
calling unsafe code without a cast. Doesn't seem like an 
improvement.


So 'shared' as a method qualifier doesn't really make sense. 
What would make more sense is to have a region where 'shared' 
data could be accessed - Maybe something like this:


struct S {
shared int x;
Lock lk;

private void addNum(int n) shared {
x += num;
}

int add(int a, int b)
{
shared {
lk.lock();
addNum(a);
addNum(b);
lk.unlock();
}
}
}

So above,
1) 'x' would be shared, and mutating it would not thread safe.


As it is now.

2) 'addNum' would have access to 'shared' data, and also be 
non-thread-safe


Today, non-`shared` methods are unsafe, and they can access 
shared data just like `shared` methods. But I imagine you'd have 
`shared` methods alter `shared` data freely, without casting.


3) 'x' and 'addNum' would be inaccessible from 'add' since 
they're 'shared'



Re: Mallocator and 'shared'

2017-02-12 Thread Moritz Maxeiner via Digitalmars-d-learn

On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
It seems that methods qualified with 'shared' may be what 
you're suggesting matches up with the 'bridge' I'm trying to 
describe, but again, using the word 'shared' to mean both 
'thread safe' and 'not thread safe' doesn't make sense. [...]


For essentially all that follows, refer to [1][2]
`shared` (as well as `__gshared`) on a variable has the semantics 
of multiple threads sharing one single memory location for that 
variable (i.e. it will not be put into thread local storage). 
Accessing such data directly is inherently not thread safe. 
Period. You will need some form of synchronization (see [3]) to 
access such data in a thread safe manner.
Now, `shared` is supposed to additionally provide memory 
barriers, so that reads/writes on such variables are guaranteed 
not to be reordered in a way that breaks your algorithm; 
remember, the compiler (and also later the cpu when it reorders 
the opcode) is allowed to reorder reads/writes to a memory 
location to be more efficient, as long as doing so won't change 
the logic as the compiler/or cpu sees it. Example:


__gshared int a = 0;

// thread 1:
a = 1;
int b = a;
writeln(b);

// thread 2:
a += 1;

In the above, you may expect `b` to be either 1, or 2, depending 
on how the cpu interleaves the memory access, but it can, in 
fact, also be 0, since neither the compiler, nor the cpu can 
detect any reason as to why `a = 1` should need to come before 
`int b = a` and may thus reorder the write and the read. Memory 
barriers prevent such reordering in the cpu and if we had made 
`a` `shared` those barriers would've been supposed to be emitted 
by the compiler (in addition to not reordering them itself). 
Unfortunately, that emission is not implemented.


From [4]:
Non-static member functions can have, in addition to the usual 
FunctionAttributes, the attributes const, immutable, shared, or 
inout. These attributes apply to the hidden this parameter.


Thus a member function being `shared` means nothing more than 
that the instance it is called on must also be `shared`, i.e.


class Foo
{
shared void bar();
}

Foo foo;
foo.bar();// this is illegal, `foo` (the hidden `this` of 
`bar`) is not shared


shared Foo foobar;
foobar.bar(); // this is legal, since `foobar` is shared

That's it, there are no two meanings of `shared` depending on 
some context, there is only one: The data in question, which is 
either the attributed variable, or the object/instance of the 
member function being attributed, is shared between threads and 
accessing it directly is not thread safe.


On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
I thought 'shared' was a finished feature, but it's starting to 
seem like it's a WIP.


I prefer the term "unfinished" since "WIP" implies that it's 
being worked on. AFAIK there's no one currently working on 
implementing what's missing in the compiler frontend with regards 
to the spec.


On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
This kind of feature seems like it has great potential, but is 
mostly useless in it's current state.


I share that opinion and generally either use `__gshared` if I 
absolutely have to share data via shared memory and design 
carefully to avoid all the potential issues, or - which I much 
prefer - use message passing: `std.concurrency` is your friend.


On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
After more testing with shared, it seems that 'shared' data is 
mutable from many contexts, from which it would be unsafe to 
mutate it without locking first, which basically removes any 
gauruntee that would make 'shared' useful.


As pointed out above that's to be expected, since that's its job. 
Regarding guarantees: Since D treats data as thread local by 
default, you need either `shared` or `__gshared` to have mutable 
shared (intra-process) memory (ignoring OS facilities for 
inter-process shared memory). The main advantage is not in data 
being `shared`/`__gshared`, but in the guarantees that all the 
other (unattributed, thread local) data gets: Each thread has its 
own copies and any optimisations applied to code that accesses 
them need not consider multiple threads (I'd wager this is a 
significant boon towards D's fast compile times).
If you only talk about useful benefits of `shared` over 
`__gshared`, if the spec were properly implemented, the useful 
properties would include you not needing to worry about memory 
barriers. Other useful guaranties are the more rigorous type 
checks, when compared to `__gshared`, which are supposed to 
prevent you from committing some of the common mistakes occurring 
in non-sequential programming (see, e.g. the code example with 
`class Foo` above).


On Sunday, 12 February 2017 at 20:08:05 UTC, bitwise wrote:
Again, tell me if I'm wrong here, but there seems to be a lot 
of holes in 'shared'.


There are holes in the implementation of `shared`; it's spec, 

Re: Mallocator and 'shared'

2017-02-12 Thread bitwise via Digitalmars-d-learn
On Saturday, 11 February 2017 at 04:32:37 UTC, Michael Coulombe 
wrote:

On Friday, 10 February 2017 at 23:57:18 UTC, bitwise wrote:

[...]


A shared method means that it can only be called on a shared 
instance of the struct/class, which will have shared fields. A 
shared method should be logically thread-safe, but that cannot 
be guaranteed by the compiler. A non-shared method can touch 
shared memory, and thus should be thread-safe if it does, but 
can only be called on a non-shared instance with possibly 
non-shared fields.


shared/non-shared methods don't mix because you generally need 
to use different, less-efficient instructions and algorithms to 
be thread-safe and scalable in a shared method. In the case of 
Mallocator, there are no fields so as far as I can tell the 
attribute doesn't do much except for documentation and for 
storing references to it in other shared structs/objects.


Thanks for the explanation, but I'm still confused.

It seems like you're saying that 'shared' should mean both 
'thread safe' and 'not thread safe' depending on context, which 
doesn't make sense.


Example:

shared A a;

struct A {
int x, y;

void foo() shared {
a.x = 1;
}
}

int main(string[] argv) {
a.x = 5;
a.y = 5;
a.foo();
return 0;
}

Qualifying 'a' with 'shared' means that it's shared between 
threads, which means that accessing it is _not_ thread safe. 
Since the method 'foo' accesses 'a', 'foo' is also _not_ thread 
safe. Given that both the data and the method are 'shared', a 
caller should know that race conditions are possible and that 
they should aquire a lock before accessing either of them...or so 
it would seem.


I imagine that qualifying a method with 'shared' should mean that 
it can access shared data, and hence, is _not_ thread safe. This 
prevent access to 'shared' data from any non 'shared' context, 
without some kind of bridge/cast that a programmer would use when 
they knew that they had aquired the lock or ownership of the 
data. Although this is what would make sense to me, it doesn't 
seem to match with the current implementation of 'shared', or 
what you're saying.


It seems that methods qualified with 'shared' may be what you're 
suggesting matches up with the 'bridge' I'm trying to describe, 
but again, using the word 'shared' to mean both 'thread safe' and 
'not thread safe' doesn't make sense. Firstly, because the same 
keyword should not mean two strictly opposite things. Also, if a 
'shared' method is supposed to be thread safe, then the fact that 
it has access to shared data is irrelevant to the caller. So 
'shared' as a method qualifier doesn't really make sense. What 
would make more sense is to have a region where 'shared' data 
could be accessed - Maybe something like this:


struct S {
shared int x;
Lock lk;

private void addNum(int n) shared {
x += num;
}

int add(int a, int b)
{
shared {
lk.lock();
addNum(a);
addNum(b);
lk.unlock();
}
}
}

So above,
1) 'x' would be shared, and mutating it would not thread safe.
2) 'addNum' would have access to 'shared' data, and also be 
non-thread-safe
3) 'x' and 'addNum' would be inaccessible from 'add' since 
they're 'shared'
4) a 'shared' block inside 'add' would allow access to 'x' or 
'addNum', with the responsibility being on the programmer to lock.
5) alternatively, 'shared' data could be accessed from within a 
'synchronized' block.


I thought 'shared' was a finished feature, but it's starting to 
seem like it's a WIP. This kind of feature seems like it has 
great potential, but is mostly useless in it's current state. 
After more testing with shared, it seems that 'shared' data is 
mutable from many contexts, from which it would be unsafe to 
mutate it without locking first, which basically removes any 
gauruntee that would make 'shared' useful.


Again, tell me if I'm wrong here, but there seems to be a lot of 
holes in 'shared'.


  Thanks


Re: Mallocator and 'shared'

2017-02-10 Thread Nicholas Wilson via Digitalmars-d-learn

On Friday, 10 February 2017 at 23:57:18 UTC, bitwise wrote:

https://github.com/dlang/phobos/blob/cd7846eb96ea7d2fa65ccb04b4ca5d5b0d1d4a63/std/experimental/allocator/mallocator.d#L63-L65

Looking at Mallocator, the use of 'shared' doesn't seem correct 
to me.


[...]


IIRC you're supposed to use `Mallocator.instance` as it is a 
singleton.


Re: Mallocator and 'shared'

2017-02-10 Thread Michael Coulombe via Digitalmars-d-learn

On Friday, 10 February 2017 at 23:57:18 UTC, bitwise wrote:

https://github.com/dlang/phobos/blob/cd7846eb96ea7d2fa65ccb04b4ca5d5b0d1d4a63/std/experimental/allocator/mallocator.d#L63-L65

Looking at Mallocator, the use of 'shared' doesn't seem correct 
to me.


The logic stated in the comment above is that 'malloc' is 
thread safe, and therefore all methods of Mallocator can be 
qualified with 'shared'.


I thought that qualifying a method as 'shared' meant that it 
_can_ touch shared memory, and is therefore _not_ thread safe.



The following program produces this error:
"Error: shared method Mallocator.allocate is not callable using 
a non-shared object"


import std.experimental.allocator.mallocator;

int main(string[] argv) {
Mallocator m;
m.allocate(64);
return 0;
}

And the above error is because it would be un(thread)safe to 
call those methods from a non-shared context, due to the fact 
that they may access shared memory.


Am I wrong here?


A shared method means that it can only be called on a shared 
instance of the struct/class, which will have shared fields. A 
shared method should be logically thread-safe, but that cannot be 
guaranteed by the compiler. A non-shared method can touch shared 
memory, and thus should be thread-safe if it does, but can only 
be called on a non-shared instance with possibly non-shared 
fields.


shared/non-shared methods don't mix because you generally need to 
use different, less-efficient instructions and algorithms to be 
thread-safe and scalable in a shared method. In the case of 
Mallocator, there are no fields so as far as I can tell the 
attribute doesn't do much except for documentation and for 
storing references to it in other shared structs/objects.


Re: Mallocator

2016-03-03 Thread Erik Smith via Digitalmars-d

On Thursday, 3 March 2016 at 20:31:47 UTC, Meta wrote:

On Thursday, 3 March 2016 at 20:16:55 UTC, Erik Smith wrote:


The later works and qualifying the allocator member variable 
shared seems to solve the issue.  Example:


struct A(T) {
alias Allocator = T;
shared Allocator allocator;
this(string url="") {
allocator = Allocator();
void *p1 = cast(void*)(allocator.allocate(1024));
//void p2[] = allocator.allocate(1024); // ICE
}
}


Does this still ICE when you write it as `void[] p2 = 
allocator.allocate(1024)`?


No.  :)



Re: Mallocator

2016-03-03 Thread Meta via Digitalmars-d

On Thursday, 3 March 2016 at 20:16:55 UTC, Erik Smith wrote:


The later works and qualifying the allocator member variable 
shared seems to solve the issue.  Example:


struct A(T) {
alias Allocator = T;
shared Allocator allocator;
this(string url="") {
allocator = Allocator();
void *p1 = cast(void*)(allocator.allocate(1024));
//void p2[] = allocator.allocate(1024); // ICE
}
}


Does this still ICE when you write it as `void[] p2 = 
allocator.allocate(1024)`?




Re: Mallocator

2016-03-03 Thread Erik Smith via Digitalmars-d

On Thursday, 3 March 2016 at 19:32:40 UTC, Brian Schott wrote:

On Thursday, 3 March 2016 at 19:01:52 UTC, Erik Smith wrote:
I get the error "allocate is not callable using a non-shared 
object" and I'm not sure how to resolve it.


Are you calling `Mallocator.allocate()` or 
`Mallocator.instance.allocate()`?


The later works and qualifying the allocator member variable 
shared seems to solve the issue.  Example:


struct A(T) {
alias Allocator = T;
shared Allocator allocator;
this(string url="") {
allocator = Allocator();
void *p1 = cast(void*)(allocator.allocate(1024));
//void p2[] = allocator.allocate(1024); // ICE
}
}

A!Mallocator a;


However, this seems bad because it assumes that all allocators 
are shared.  It appears, however that the qualifier can be 
attached to the type so maybe there is some meta-programming 
tricks that can be used:


alias Allocator = shared T;

I'm not sure if I'm on the right track.  Note also the ICE in the 
example above.


erik





Re: Mallocator

2016-03-03 Thread Brian Schott via Digitalmars-d

On Thursday, 3 March 2016 at 19:01:52 UTC, Erik Smith wrote:
I get the error "allocate is not callable using a non-shared 
object" and I'm not sure how to resolve it.


Are you calling `Mallocator.allocate()` or 
`Mallocator.instance.allocate()`?