November 13, 2012
On 11/13/12 2:22 PM, Walter Bright wrote:
> On 11/13/2012 1:56 PM, Andrei Alexandrescu wrote:
>> On 11/13/12 1:28 PM, Walter Bright wrote:
>>> On 11/13/2012 1:11 AM, luka8088 wrote:
>>>> This clarifies a lot, but still a lot of people get confused with:
>>>> http://dlang.org/faq.html#shared_memory_barriers
>>>> is it a faq error ?
>>>
>>> Andrei is a proponent of having shared to memory barriers, I disagree
>>> with him. We haven't convinced each other yet, so this is a bit up in
>>> the air.
>>
>> Wait, then what would shared do? This is new to me as I've always
>> assumed you
>> and I have the same view on this.
>
> I'm just not convinced that having the compiler add memory barriers:
>
> 1. will result in correctly working code, when done by programmers who
> have only an incomplete understanding of memory barriers, which would be
> about 99.9% of us.
>
> 2. will result in efficient code

I'm fine with these arguments. We'll need to break current uses of shared then. What you say is that essentially you can't do even this:

shared int x;
...
x = 4;

You'll need to use x.load(4) instead.

Just for the record I'm okay with this breakage.

> I also worry that it will lure programmers into a false sense of
> complacency about shared, that simply adding "shared" to a type will
> make their concurrent code work. Few seem to realize that adding memory
> barriers only makes code sequentially consistent, it does *not*
> eliminate race conditions.

It does eliminate all low-level races.

> It just turns a multicore machine into
> (logically) a single core one, *not* a single threaded one.

This is very approximate.

> But I do see enormous value in shared in that it logically (and rather
> forcefully) separates thread-local code from multi-thread code. For
> example, see the post here about adding a destructor to a shared struct,
> and having it fail to compile. The complaint was along the lines of
> shared being broken, whereas I viewed it along the lines of shared
> pointing out a logic problem in the code - what does destroying a struct
> accessible from multiple threads mean? I think it must be clear that
> destroying an object can only happen in one thread, i.e. the object must
> become thread local in order to be destroyed.

As long as a cast is required along the way, we can't claim victory. I need to think about that scenario.


Andrei
November 13, 2012
On Tuesday, 13 November 2012 at 22:33:51 UTC, Andrei Alexandrescu wrote:
> shared int x;
> ...
> x = 4;
>
> You'll need to use x.load(4) instead.

You mean x.store(4)? Or am I completely misunderstanding your message?

David
November 13, 2012
On 11/13/12 3:07 PM, David Nadlinger wrote:
> On Tuesday, 13 November 2012 at 22:33:51 UTC, Andrei Alexandrescu wrote:
>> shared int x;
>> ...
>> x = 4;
>>
>> You'll need to use x.load(4) instead.
>
> You mean x.store(4)? Or am I completely misunderstanding your message?
>
> David

Apologies, yes, store.

Andrei
November 13, 2012
On 13-11-2012 23:33, Andrei Alexandrescu wrote:
> On 11/13/12 2:22 PM, Walter Bright wrote:
>> On 11/13/2012 1:56 PM, Andrei Alexandrescu wrote:
>>> On 11/13/12 1:28 PM, Walter Bright wrote:
>>>> On 11/13/2012 1:11 AM, luka8088 wrote:
>>>>> This clarifies a lot, but still a lot of people get confused with:
>>>>> http://dlang.org/faq.html#shared_memory_barriers
>>>>> is it a faq error ?
>>>>
>>>> Andrei is a proponent of having shared to memory barriers, I disagree
>>>> with him. We haven't convinced each other yet, so this is a bit up in
>>>> the air.
>>>
>>> Wait, then what would shared do? This is new to me as I've always
>>> assumed you
>>> and I have the same view on this.
>>
>> I'm just not convinced that having the compiler add memory barriers:
>>
>> 1. will result in correctly working code, when done by programmers who
>> have only an incomplete understanding of memory barriers, which would be
>> about 99.9% of us.
>>
>> 2. will result in efficient code
>
> I'm fine with these arguments. We'll need to break current uses of
> shared then. What you say is that essentially you can't do even this:
>
> shared int x;
> ...
> x = 4;
>
> You'll need to use x.load(4) instead.

Is that meant to be an atomic store, or just a regular, but explicit, store?

(I know you meant store.)

>
> Just for the record I'm okay with this breakage.
>
>> I also worry that it will lure programmers into a false sense of
>> complacency about shared, that simply adding "shared" to a type will
>> make their concurrent code work. Few seem to realize that adding memory
>> barriers only makes code sequentially consistent, it does *not*
>> eliminate race conditions.
>
> It does eliminate all low-level races.
>
>> It just turns a multicore machine into
>> (logically) a single core one, *not* a single threaded one.
>
> This is very approximate.
>
>> But I do see enormous value in shared in that it logically (and rather
>> forcefully) separates thread-local code from multi-thread code. For
>> example, see the post here about adding a destructor to a shared struct,
>> and having it fail to compile. The complaint was along the lines of
>> shared being broken, whereas I viewed it along the lines of shared
>> pointing out a logic problem in the code - what does destroying a struct
>> accessible from multiple threads mean? I think it must be clear that
>> destroying an object can only happen in one thread, i.e. the object must
>> become thread local in order to be destroyed.
>
> As long as a cast is required along the way, we can't claim victory. I
> need to think about that scenario.
>
>
> Andrei


-- 
Alex Rønne Petersen
alex@lycus.org
http://lycus.org
November 13, 2012
On 11/13/12 3:28 PM, Alex Rønne Petersen wrote:
> On 13-11-2012 23:33, Andrei Alexandrescu wrote:
>> shared int x;
>> ...
>> x = 4;
>>
>> You'll need to use x.store(4) instead.
>
> Is that meant to be an atomic store, or just a regular, but explicit,
> store?

Atomic and sequentially consistent.


Andrei
November 13, 2012
On 14-11-2012 00:38, Andrei Alexandrescu wrote:
> On 11/13/12 3:28 PM, Alex Rønne Petersen wrote:
>> On 13-11-2012 23:33, Andrei Alexandrescu wrote:
>>> shared int x;
>>> ...
>>> x = 4;
>>>
>>> You'll need to use x.store(4) instead.
>>
>> Is that meant to be an atomic store, or just a regular, but explicit,
>> store?
>
> Atomic and sequentially consistent.
>
>
> Andrei

OK, but then we have the problem I presented in the OP: This only works for certain types, on certain architectures, for certain processors, ...

So, we could limit shared load/store to only work on certain types and require all architectures that D compilers target to provide those. *But* this means that shared on any non-primitive types becomes essentially useless and will in 99% of cases just be casted away. On the other hand, if we make it implementation-defined, people end up writing highly unportable code. So, (unless anyone can come up with better alternatives), I think guaranteeing atomic load/store for a certain set of types is the most sensible way forward.

FWIW, these are the types and type categories I'd expect shared load/store to work on, on any architecture:

* ubyte, byte
* ushort, short
* uint, int
* ulong, long
* float, double
* pointers
* slices
* references
* function pointers
* delegates

-- 
Alex Rønne Petersen
alex@lycus.org
http://lycus.org
November 13, 2012
On 14-11-2012 00:43, Alex Rønne Petersen wrote:
> On 14-11-2012 00:38, Andrei Alexandrescu wrote:
>> On 11/13/12 3:28 PM, Alex Rønne Petersen wrote:
>>> On 13-11-2012 23:33, Andrei Alexandrescu wrote:
>>>> shared int x;
>>>> ...
>>>> x = 4;
>>>>
>>>> You'll need to use x.store(4) instead.
>>>
>>> Is that meant to be an atomic store, or just a regular, but explicit,
>>> store?
>>
>> Atomic and sequentially consistent.
>>
>>
>> Andrei
>
> OK, but then we have the problem I presented in the OP: This only works
> for certain types, on certain architectures, for certain processors, ...
>
> So, we could limit shared load/store to only work on certain types and
> require all architectures that D compilers target to provide those.
> *But* this means that shared on any non-primitive types becomes
> essentially useless and will in 99% of cases just be casted away. On the
> other hand, if we make it implementation-defined, people end up writing
> highly unportable code. So, (unless anyone can come up with better
> alternatives), I think guaranteeing atomic load/store for a certain set
> of types is the most sensible way forward.
>
> FWIW, these are the types and type categories I'd expect shared
> load/store to work on, on any architecture:
>
> * ubyte, byte
> * ushort, short
> * uint, int
> * ulong, long
> * float, double
> * pointers
> * slices
> * references
> * function pointers
> * delegates
>

Scratch that, make it this:

* ubyte, byte
* ushort, short
* uint, int
* ulong, long
* float, double
* pointers
* references
* function pointers

Slices and delegates can't be loaded/stored atomically because very few architectures provide instructions to atomically load/store 16 bytes of data (required on 64-bit; 32-bit would be fine since that's just 8 bytes, but portability is king). This is also why ucent, cent, and real are not included in the list.

-- 
Alex Rønne Petersen
alex@lycus.org
http://lycus.org
November 13, 2012
Le 13/11/2012 23:07, Peter Alexander a écrit :
> On Tuesday, 13 November 2012 at 21:56:21 UTC, Andrei Alexandrescu wrote:
>> On 11/13/12 1:28 PM, Walter Bright wrote:
>>> On 11/13/2012 1:11 AM, luka8088 wrote:
>>>> This clarifies a lot, but still a lot of people get confused with:
>>>> http://dlang.org/faq.html#shared_memory_barriers
>>>> is it a faq error ?
>>>
>>> Andrei is a proponent of having shared to memory barriers, I disagree
>>> with him. We haven't convinced each other yet, so this is a bit up in
>>> the air.
>>
>> Wait, then what would shared do? This is new to me as I've always
>> assumed you and I have the same view on this.
>>
>> Andrei
>
> I'm speaking out of turn, but...
>
> I'll flip that around: what would shared do if there were memory barriers?
>
> Walter has said previously in this thread that shared is to be used to
> mark shared data, and disallow any potentially non-thread-safe
> operations. To use shared data, you need to manually lock it and then
> cast away the shared temporarily. This can be made more pleasant with
> library utilities.
>

It cannot unless some ownership is introduced in D.
November 14, 2012
Le 13/11/2012 23:22, Walter Bright a écrit :
> I'm just not convinced that having the compiler add memory barriers:
>
> 1. will result in correctly working code, when done by programmers who
> have only an incomplete understanding of memory barriers, which would be
> about 99.9% of us.
>
> 2. will result in efficient code
>
> I also worry that it will lure programmers into a false sense of
> complacency about shared, that simply adding "shared" to a type will
> make their concurrent code work. Few seem to realize that adding memory
> barriers only makes code sequentially consistent, it does *not*
> eliminate race conditions. It just turns a multicore machine into
> (logically) a single core one, *not* a single threaded one.
>

That is what java's volatile do. It have several uses cases, including valid double check locking (It has to be noted that this idiom is used incorrectly in druntime ATM, which proves both its usefullness and that it require language support) and disruptor which I wanted to implement for message passing in D but couldn't because of lack of support at the time.

See: http://www.slideshare.net/trishagee/introduction-to-the-disruptor

So sequentially consistent read/write are usefull.

> But I do see enormous value in shared in that it logically (and rather
> forcefully) separates thread-local code from multi-thread code. For
> example, see the post here about adding a destructor to a shared struct,
> and having it fail to compile. The complaint was along the lines of
> shared being broken, whereas I viewed it along the lines of shared
> pointing out a logic problem in the code - what does destroying a struct
> accessible from multiple threads mean? I think it must be clear that
> destroying an object can only happen in one thread, i.e. the object must
> become thread local in order to be destroyed.
>

This struct stuff don't make any sense to me. Java, C# and many other language multithread, have everything shared and still are able to have finalizer of some sort.

Why couldn't a shared object be destroyed ? Why should it be destroyed in a specific thread as it can only refer shared data because of transitivity ?
November 14, 2012
Le 14/11/2012 00:43, Alex Rønne Petersen a écrit :
> On 14-11-2012 00:38, Andrei Alexandrescu wrote:
>> On 11/13/12 3:28 PM, Alex Rønne Petersen wrote:
>>> On 13-11-2012 23:33, Andrei Alexandrescu wrote:
>>>> shared int x;
>>>> ...
>>>> x = 4;
>>>>
>>>> You'll need to use x.store(4) instead.
>>>
>>> Is that meant to be an atomic store, or just a regular, but explicit,
>>> store?
>>
>> Atomic and sequentially consistent.
>>
>>
>> Andrei
>
> OK, but then we have the problem I presented in the OP: This only works
> for certain types, on certain architectures, for certain processors, ...
>
> So, we could limit shared load/store to only work on certain types and
> require all architectures that D compilers target to provide those.
> *But* this means that shared on any non-primitive types becomes
> essentially useless and will in 99% of cases just be casted away. On the
> other hand, if we make it implementation-defined, people end up writing
> highly unportable code. So, (unless anyone can come up with better
> alternatives), I think guaranteeing atomic load/store for a certain set
> of types is the most sensible way forward.
>
> FWIW, these are the types and type categories I'd expect shared
> load/store to work on, on any architecture:
>
> * ubyte, byte
> * ushort, short
> * uint, int
> * ulong, long
> * float, double
> * pointers
> * slices
> * references
> * function pointers
> * delegates
>

I wouldn't expected it to work for delegates, long, ulong, double and slice on every arch. If it does work, that is awesome, and add to my determination that this is the thing to do.