November 16, 2012
On 11/15/12 3:05 PM, David Nadlinger wrote:
> On Thursday, 15 November 2012 at 22:57:54 UTC, Andrei Alexandrescu wrote:
>> On 11/15/12 1:29 PM, David Nadlinger wrote:
>>> On Wednesday, 14 November 2012 at 17:54:16 UTC, Andrei Alexandrescu
>>> wrote:
>>>> That is correct. My point is that compiler implementers would follow
>>>> some specification. That specification would contain informationt hat
>>>> atomicLoad and atomicStore must have special properties that put them
>>>> apart from any other functions.
>>>
>>> What are these special properties? Sorry, it seems like we are talking
>>> past each other…
>>
>> For example you can't hoist a memory operation before a shared load or
>> after a shared store.
>
> Well, to be picky, that depends on what kind of memory operation you
> mean – moving non-volatile loads/stores across volatile ones is
> typically considered acceptable.

In D that's fine (as long as in-thread SC is respected) because non-shared vars are guaranteed to be thread-local.

> But still, you can't move memory operations across any other arbitrary
> function call either (unless you can prove it is safe by inspecting the
> callee's body, obviously), so I don't see where atomicLoad/atomicStore
> would be special here.

It is special because e.g. on x86 the function is often a simple unprotected load or store. So after the inliner has at it, there's nothing to stay in the way of reordering. The point is the compiler must understand the semantics of acquire and release.


Andrei
November 16, 2012
On 11/15/12 3:30 PM, David Nadlinger wrote:
> On Thursday, 15 November 2012 at 23:22:32 UTC, Sean Kelly wrote:
>> On Nov 15, 2012, at 3:05 PM, David Nadlinger <see@klickverbot.at> wrote:
>>> Well, to be picky, that depends on what kind of memory operation you
>>> mean – moving non-volatile loads/stores across volatile ones is
>>> typically considered acceptable.
>>
>> Usually not, really. Like if you implement a mutex, you don't want
>> non-volatile operations to be hoisted above the mutex acquire or sunk
>> below the mutex release. However, it's safe to move additional
>> operations into the block where the mutex is held.
>
> Oh well, I was just being stupid when typing up my response: What I
> meant to say is that you _can_ reorder a set of memory operations
> involving atomic/volatile ones unless you violate the guarantees of the
> chosen memory order option.
>
> So, for Andrei's statement to be true, shared needs to be defined as
> making all memory operations sequentially consistent. Walter doesn't
> seem to think this is the way to go, at least if that is what he is
> referring to as »memory barriers«.

Shared must be sequentially consistent.

Andrei


November 16, 2012
On 15 November 2012 17:17, Andrei Alexandrescu < SeeWebsiteForEmail@erdani.org> wrote:

> On 11/15/12 1:08 AM, Manu wrote:
>
>> On 14 November 2012 19:54, Andrei Alexandrescu
>> <SeeWebsiteForEmail@erdani.org <mailto:SeeWebsiteForEmail@**erdani.org<SeeWebsiteForEmail@erdani.org>
>> >>
>>
>> wrote:
>>     Yah, the whole point here is that we need something IN THE LANGUAGE
>>     DEFINITION about atomicLoad and atomicStore. NOT IN THE
>> IMPLEMENTATION.
>>
>>     THIS IS VERY IMPORTANT.
>>
>>
>> I won't outright disagree, but this seems VERY dangerous to me.
>>
>> You need to carefully study all popular architectures, and consider that if the language is made to depend on these primitives, and the architecture doesn't support it, or support that particular style of implementation (fairly likely), than D will become incompatible with a huge number of architectures on that day.
>>
>
> All contemporary languages that are serious about concurrency support atomic primitives one way or another. We must too. There's no two ways about it.
>
> [snip]
>
>> Side note: I still think a convenient and fairly practical solution is
>> to make 'shared' things 'lockable'; where you can lock()/unlock() them,
>> and assignment to/from shared things is valid (no casting), but a
>> runtime assert insists that the entity is locked whenever it is
>> accessed.
>>
>
> This (IIUC) is conflating mutex-based synchronization with memory models and atomic operations. I suggest we postpone anything related to that for the sake of staying focused.


I'm not conflating the 2, I'm suggesting to stick with the primitives that
are already present and proven, at least for the time being.
This thread is about addressing the problem in the short term, long term
plans can simmer until they're ready, but any moves in the short term
should make use of the primitives available and known to work, ie, don't
try and weave in language level support for architectural atomic operations
until there's a thoroughly detailed plan, and it's validated against many
architectures so we know what we're losing.
Libraries can already be written to do a lot of atomic stuff, but I still
agree with the OP that shared should be addressed and made more useful in
the short term, hence my simplistic suggestion; runtime assert that a
shared object is locked when it is read/written, and consequently, lift the
cast requirement, making it compatible with templates.


November 16, 2012
On 15 November 2012 17:17, Andrei Alexandrescu < SeeWebsiteForEmail@erdani.org> wrote:

> On 11/15/12 1:08 AM, Manu wrote:
>
>> On 14 November 2012 19:54, Andrei Alexandrescu
>> <SeeWebsiteForEmail@erdani.org <mailto:SeeWebsiteForEmail@**erdani.org<SeeWebsiteForEmail@erdani.org>
>> >>
>>
>> wrote:
>>     Yah, the whole point here is that we need something IN THE LANGUAGE
>>     DEFINITION about atomicLoad and atomicStore. NOT IN THE
>> IMPLEMENTATION.
>>
>>     THIS IS VERY IMPORTANT.
>>
>>
>> I won't outright disagree, but this seems VERY dangerous to me.
>>
>> You need to carefully study all popular architectures, and consider that if the language is made to depend on these primitives, and the architecture doesn't support it, or support that particular style of implementation (fairly likely), than D will become incompatible with a huge number of architectures on that day.
>>
>
> All contemporary languages that are serious about concurrency support atomic primitives one way or another. We must too. There's no two ways about it.
>

I can't resist... D may be serious about the *idea* of concurrency, but it
clearly isn't serious about concurrency yet. shared is a prime example of
that.
We do support atomic primitives 'one way or another'; there are intrinsics
on all compilers. Libraries can use them.
Again, this thread seemed to be about urgent action... D needs a LOT of
work on it's concurrency model, but something of an urgent fix to make a
key language feature more useful needs to leverage what's there now.


November 16, 2012
On Friday, 16 November 2012 at 09:24:22 UTC, Manu wrote:
> On 15 November 2012 17:17, Andrei Alexandrescu <
> SeeWebsiteForEmail@erdani.org> wrote:
>
>> On 11/15/12 1:08 AM, Manu wrote:
>>
>>> On 14 November 2012 19:54, Andrei Alexandrescu
>>> <SeeWebsiteForEmail@erdani.org <mailto:SeeWebsiteForEmail@**erdani.org<SeeWebsiteForEmail@erdani.org>
>>> >>
>>>
>>> wrote:
>>>     Yah, the whole point here is that we need something IN THE LANGUAGE
>>>     DEFINITION about atomicLoad and atomicStore. NOT IN THE
>>> IMPLEMENTATION.
>>>
>>>     THIS IS VERY IMPORTANT.
>>>
>>>
>>> I won't outright disagree, but this seems VERY dangerous to me.
>>>
>>> You need to carefully study all popular architectures, and consider that
>>> if the language is made to depend on these primitives, and the
>>> architecture doesn't support it, or support that particular style of
>>> implementation (fairly likely), than D will become incompatible with a
>>> huge number of architectures on that day.
>>>
>>
>> All contemporary languages that are serious about concurrency support
>> atomic primitives one way or another. We must too. There's no two ways
>> about it.
>>
>> [snip]
>>
>>> Side note: I still think a convenient and fairly practical solution is
>>> to make 'shared' things 'lockable'; where you can lock()/unlock() them,
>>> and assignment to/from shared things is valid (no casting), but a
>>> runtime assert insists that the entity is locked whenever it is
>>> accessed.
>>>
>>
>> This (IIUC) is conflating mutex-based synchronization with memory models
>> and atomic operations. I suggest we postpone anything related to that for
>> the sake of staying focused.
>
>
> I'm not conflating the 2, I'm suggesting to stick with the primitives that
> are already present and proven, at least for the time being.
> This thread is about addressing the problem in the short term, long term
> plans can simmer until they're ready, but any moves in the short term
> should make use of the primitives available and known to work, ie, don't
> try and weave in language level support for architectural atomic operations
> until there's a thoroughly detailed plan, and it's validated against many
> architectures so we know what we're losing.
> Libraries can already be written to do a lot of atomic stuff, but I still
> agree with the OP that shared should be addressed and made more useful in
> the short term, hence my simplistic suggestion; runtime assert that a
> shared object is locked when it is read/written, and consequently, lift the
> cast requirement, making it compatible with templates.

Seems to me that Soenkes's library solution went into to right direction

http://forum.dlang.org/post/k831b6$1368$1@digitalmars.com


November 16, 2012
On 16 November 2012 12:09, Pragma Tix <bizprac@orange.fr> wrote:

> On Friday, 16 November 2012 at 09:24:22 UTC, Manu wrote:
>
>> On 15 November 2012 17:17, Andrei Alexandrescu < SeeWebsiteForEmail@erdani.org> wrote:
>>
>>  On 11/15/12 1:08 AM, Manu wrote:
>>>
>>>  On 14 November 2012 19:54, Andrei Alexandrescu
>>>> <SeeWebsiteForEmail@erdani.org <mailto:SeeWebsiteForEmail@**e**
>>>> rdani.org <http://erdani.org><SeeWebsiteForEmail@**erdani.org<SeeWebsiteForEmail@erdani.org>
>>>> >
>>>>
>>>> >>
>>>>
>>>> wrote:
>>>>     Yah, the whole point here is that we need something IN THE LANGUAGE
>>>>     DEFINITION about atomicLoad and atomicStore. NOT IN THE
>>>> IMPLEMENTATION.
>>>>
>>>>     THIS IS VERY IMPORTANT.
>>>>
>>>>
>>>> I won't outright disagree, but this seems VERY dangerous to me.
>>>>
>>>> You need to carefully study all popular architectures, and consider that if the language is made to depend on these primitives, and the architecture doesn't support it, or support that particular style of implementation (fairly likely), than D will become incompatible with a huge number of architectures on that day.
>>>>
>>>>
>>> All contemporary languages that are serious about concurrency support atomic primitives one way or another. We must too. There's no two ways about it.
>>>
>>> [snip]
>>>
>>>  Side note: I still think a convenient and fairly practical solution is
>>>> to make 'shared' things 'lockable'; where you can lock()/unlock() them,
>>>> and assignment to/from shared things is valid (no casting), but a
>>>> runtime assert insists that the entity is locked whenever it is
>>>> accessed.
>>>>
>>>>
>>> This (IIUC) is conflating mutex-based synchronization with memory models and atomic operations. I suggest we postpone anything related to that for the sake of staying focused.
>>>
>>
>>
>> I'm not conflating the 2, I'm suggesting to stick with the primitives that
>> are already present and proven, at least for the time being.
>> This thread is about addressing the problem in the short term, long term
>> plans can simmer until they're ready, but any moves in the short term
>> should make use of the primitives available and known to work, ie, don't
>> try and weave in language level support for architectural atomic
>> operations
>> until there's a thoroughly detailed plan, and it's validated against many
>> architectures so we know what we're losing.
>> Libraries can already be written to do a lot of atomic stuff, but I still
>> agree with the OP that shared should be addressed and made more useful in
>> the short term, hence my simplistic suggestion; runtime assert that a
>> shared object is locked when it is read/written, and consequently, lift
>> the
>> cast requirement, making it compatible with templates.
>>
>
> Seems to me that Soenkes's library solution went into to right direction
>
> http://forum.dlang.org/post/**k831b6$1368$1@digitalmars.com<http://forum.dlang.org/post/k831b6$1368$1@digitalmars.com>
>

Looks reasonable to me, also Dmitry Olshansky and luka have both made
suggestions that look good to me aswell.
I think the only problem with all these is that they don't really feel like
a feature of the language, just some template that's not yet even in the
library.
D likes to claim that it is strong on concurrency, with that in mind, I'd
expect to at least see one of these approaches polished, and probably even
nicely sugared.
That's a minimum that people will expect, it's a proven, well known pattern
that many are familiar with, and it can be done in the language right now.
Sugaring a feature like that is simply about improving clarity, and
reducing friction for users of something that D likes to advertise as being
a core feature of the language.


November 16, 2012
On 2012-11-15 16:08:35 +0000, Dmitry Olshansky <dmitry.olsh@gmail.com> said:

> 11/15/2012 8:33 AM, Michel Fortin пишет:
> 
>> If you want to declare the mutex separately, you could do it by
>> specifying a variable instead of a type in the variable declaration:
>> 
>>      Mutex m;
>>      synchronized(m) int i;
>> 
>>      synchronized(i)
>>      {
>>          // implicit: m.lock();
>>          // implicit: scope (exit) m.unlock();
>>          i++;
>>      }
> 
> While the rest of proposal was more or less fine. I don't get why we need escape control of mutex at all - in any case it just opens a possibility to shout yourself in the foot.

In case you want to protect two variables (or more) with the same mutex. For instance:

	Mutex m;
	synchronized(m) int next_id;
	synchronized(m) Object[int] objects_by_id;

	int addObject(Object o)
	{
		synchronized(next_id, objects_by_id)
			return objects_by_id[next_id++] = o;
	}

Here it doesn't make sense and is less efficient to have two mutexes, since every time you need to lock on next_id you'll also want to lock on objects_by_id.

I'm not sure how you could shoot yourself in the foot with this. You might get worse performance if you reuse the same mutex for too many things, just like you might get better performance if you use it wisely.


> But anyway we can make it in the library right about now.
> 
> synchronized T ---> Synchronized!T
> synchronized(i){ ... } --->
> 
> i.access((x){
> //will lock & cast away shared T inside of it
> 	...
> });
> 
> I fail to see what it doesn't solve (aside of syntactic sugar).

It solves the problem too. But it's significantly more inconvenient to use. Here's my example above redone using Syncrhonized!T:

	Synchronized!(Tuple!(int, Object[int])) objects_by_id;

	int addObject(Object o)
	{
		int id;
		objects_by_id.access((obj_by_id){
			id = obj_by_id[1][obj_by_id[0]++] = o;
		};
		return id;
	}

I'm not sure if I have to explain why I prefer the first one or not, to me it's pretty obvious.


> The key point is that Synchronized!T is otherwise an opaque type.
> We could pack a few other simple primitives like 'load', 'store' etc. All of them will go through lock-unlock.

Our proposals are pretty much identical. Your works by wrapping a variable in a struct template, mine is done with a policy object/struct associated with a variable. They'll produce the same code and impose the same restrictions.


> Even escaping a reference can be solved by passing inside of 'access'
> a proxy of T. It could even asserts that the lock is in indeed locked.

Only if you can make a proxy object that cannot leak a reference. It's already not obvious how to not leak the top-level reference, but we must also consider the case where you're protecting a data structure with the mutex and get a pointer to one of its part, like if you slice a container.

This is a hard problem. The language doesn't have a solution to that yet. However, having the link between the access policy and the variable known by the compiler makes it easier patch the hole later.

What bothers me currently is that because we want to patch all the holes while not having all the necessary tools in the language to avoid escaping references, we just make using mutexes and things alike impossible without casts at every corner, which makes things even more bug prone than being able to escape references in the first place.

There are many perils in concurrency, and the compiler cannot protect you from them all. It is of the uttermost importance that code dealing with mutexes be both readable and clear about what it is doing. Casts in this context are an obfuscator.


> Same goes about Atomic!T. Though the set of primitives is quite limited depending on T.
> (I thought that built-in shared(T) is already atomic though so no need to reinvent this wheel)
> 
> It's time we finally agree that 'shared' qualifier is an assembly language of multi-threading based on sharing. It just needs some safe patterns in the library.
> 
> That and clarifying explicitly what guarantees (aside from being well.. being shared) it provides w.r.t. memory model.
> 
> Until reaching this thread I was under impression that shared means:
> - globally visible
> - atomic operations for stuff that fits in one word
> - sequentially consistent guarantee
> - any other forms of access are disallowed except via casts

Built-in shared(T) atomicity (sequential consistency) is a subject of debate in this thread. It is not clear to me what will be the conclusion, but the way I see things atomicity is just one of the many policies you may want to use for keeping consistency when sharing data between threads.

I'm not trilled by the idea of making everything atomic by default. That'll lure users to the bug-prone expert-only path while relegating the more generally applicable protection systems (mutexes) as a second-class citizen. I think it's better that you just can't do anything with shared, or that shared simply disappear, and that those variables that must be shared be accessible only through some kind of access policy. Atomic access should be one of those access policies, on an equal footing with other ones.

But if D2 is still "frozen" -- as it was meant to be when TDPL got out -- and only minor changes can be made to it now, I don't see much hope for its concurrency model. Your Syncronized!T and Atomic!T wrappers might be the best thing we can hope for, but they're nothing to set D apart from its rivals (I could implement that easily in C++ for instance).

-- 
Michel Fortin
michel.fortin@michelf.ca
http://michelf.ca/

November 16, 2012
Am 16.11.2012 14:17, schrieb Michel Fortin:
> 
> Only if you can make a proxy object that cannot leak a reference. It's already not obvious how to not leak the top-level reference, but we must also consider the case where you're protecting a data structure with the mutex and get a pointer to one of its part, like if you slice a container.
> 
> This is a hard problem. The language doesn't have a solution to that yet. However, having the link between the access policy and the variable known by the compiler makes it easier patch the hole later.
> 
> What bothers me currently is that because we want to patch all the holes while not having all the necessary tools in the language to avoid escaping references, we just make using mutexes and things alike impossible without casts at every corner, which makes things even more bug prone than being able to escape references in the first place.
> 
> There are many perils in concurrency, and the compiler cannot protect you from them all. It is of the uttermost importance that code dealing with mutexes be both readable and clear about what it is doing. Casts in this context are an obfuscator.
> 

Can you have a look at my thread about this? http://forum.dlang.org/thread/k831b6$1368$1@digitalmars.com

I would of course favor a nicely integrated language solution that is able to lift as many restrictions as possible, while still keeping everything statically verified [I would also like to have a language solution to Rebindable!T ;)]. But as an alternative to just a years lasting discussion, which does not lead to any agreed upon solution, I'd much rather have such a library solution - it can do a lot, is reasonably pretty, and is (supposedly and with a small exception) fully safe.

November 16, 2012
On Friday, 16 November 2012 at 10:59:02 UTC, Manu wrote:
> On 16 November 2012 12:09, Pragma Tix <bizprac@orange.fr> wrote:
>
>> On Friday, 16 November 2012 at 09:24:22 UTC, Manu wrote:
>>
>>> On 15 November 2012 17:17, Andrei Alexandrescu <
>>> SeeWebsiteForEmail@erdani.org> wrote:
>>>
>>>  On 11/15/12 1:08 AM, Manu wrote:
>>>>
>>>>  On 14 November 2012 19:54, Andrei Alexandrescu
>>>>> <SeeWebsiteForEmail@erdani.org <mailto:SeeWebsiteForEmail@**e**
>>>>> rdani.org <http://erdani.org><SeeWebsiteForEmail@**erdani.org<SeeWebsiteForEmail@erdani.org>
>>>>> >
>>>>>
>>>>> >>
>>>>>
>>>>> wrote:
>>>>>     Yah, the whole point here is that we need something IN THE LANGUAGE
>>>>>     DEFINITION about atomicLoad and atomicStore. NOT IN THE
>>>>> IMPLEMENTATION.
>>>>>
>>>>>     THIS IS VERY IMPORTANT.
>>>>>
>>>>>
>>>>> I won't outright disagree, but this seems VERY dangerous to me.
>>>>>
>>>>> You need to carefully study all popular architectures, and consider that
>>>>> if the language is made to depend on these primitives, and the
>>>>> architecture doesn't support it, or support that particular style of
>>>>> implementation (fairly likely), than D will become incompatible with a
>>>>> huge number of architectures on that day.
>>>>>
>>>>>
>>>> All contemporary languages that are serious about concurrency support
>>>> atomic primitives one way or another. We must too. There's no two ways
>>>> about it.
>>>>
>>>> [snip]
>>>>
>>>>  Side note: I still think a convenient and fairly practical solution is
>>>>> to make 'shared' things 'lockable'; where you can lock()/unlock() them,
>>>>> and assignment to/from shared things is valid (no casting), but a
>>>>> runtime assert insists that the entity is locked whenever it is
>>>>> accessed.
>>>>>
>>>>>
>>>> This (IIUC) is conflating mutex-based synchronization with memory models
>>>> and atomic operations. I suggest we postpone anything related to that for
>>>> the sake of staying focused.
>>>>
>>>
>>>
>>> I'm not conflating the 2, I'm suggesting to stick with the primitives that
>>> are already present and proven, at least for the time being.
>>> This thread is about addressing the problem in the short term, long term
>>> plans can simmer until they're ready, but any moves in the short term
>>> should make use of the primitives available and known to work, ie, don't
>>> try and weave in language level support for architectural atomic
>>> operations
>>> until there's a thoroughly detailed plan, and it's validated against many
>>> architectures so we know what we're losing.
>>> Libraries can already be written to do a lot of atomic stuff, but I still
>>> agree with the OP that shared should be addressed and made more useful in
>>> the short term, hence my simplistic suggestion; runtime assert that a
>>> shared object is locked when it is read/written, and consequently, lift
>>> the
>>> cast requirement, making it compatible with templates.
>>>
>>
>> Seems to me that Soenkes's library solution went into to right direction
>>
>> http://forum.dlang.org/post/**k831b6$1368$1@digitalmars.com<http://forum.dlang.org/post/k831b6$1368$1@digitalmars.com>
>>
>
> Looks reasonable to me, also Dmitry Olshansky and luka have both made
> suggestions that look good to me aswell.
> I think the only problem with all these is that they don't really feel like
> a feature of the language, just some template that's not yet even in the
> library.
> D likes to claim that it is strong on concurrency, with that in mind, I'd
> expect to at least see one of these approaches polished, and probably even
> nicely sugared.
> That's a minimum that people will expect, it's a proven, well known pattern
> that many are familiar with, and it can be done in the language right now.
> Sugaring a feature like that is simply about improving clarity, and
> reducing friction for users of something that D likes to advertise as being
> a core feature of the language.

Hi Manu,
point taken. But Dimitry and Luka just made suggestions. Soenke offers something concrete. (working right NOW) I am afraid that we'll end up in a situation similar to the std.collections opera. Just bla bla, and zero results. (And the collection situation isn't solved since the very beginning of D, not to talk about immutable collections)

Probably not En Vogue : For me Transactional Memory Management makes sense.


November 16, 2012
On Nov 16, 2012, at 5:17 AM, Michel Fortin <michel.fortin@michelf.ca> wrote:

> On 2012-11-15 16:08:35 +0000, Dmitry Olshansky <dmitry.olsh@gmail.com> said:
> 
>> 11/15/2012 8:33 AM, Michel Fortin пишет:
>>> If you want to declare the mutex separately, you could do it by
>>> specifying a variable instead of a type in the variable declaration:
>>>     Mutex m;
>>>     synchronized(m) int i;
>>>     synchronized(i)
>>>     {
>>>         // implicit: m.lock();
>>>         // implicit: scope (exit) m.unlock();
>>>         i++;
>>>     }
>> While the rest of proposal was more or less fine. I don't get why we need escape control of mutex at all - in any case it just opens a possibility to shout yourself in the foot.
> 
> In case you want to protect two variables (or more) with the same mutex. For instance:
> 
> 	Mutex m;
> 	synchronized(m) int next_id;
> 	synchronized(m) Object[int] objects_by_id;
> 
> 	int addObject(Object o)
> 	{
> 		synchronized(next_id, objects_by_id)
> 			return objects_by_id[next_id++] = o;
> 	}
> 
> Here it doesn't make sense and is less efficient to have two mutexes, since every time you need to lock on next_id you'll also want to lock on objects_by_id.
> 
> I'm not sure how you could shoot yourself in the foot with this. You might get worse performance if you reuse the same mutex for too many things, just like you might get better performance if you use it wisely.

This is what setSameMutex was intended for in Druntime.  Except that no one uses it and people have requested that it be removed.  Perhaps that's because the semantics aren't great though.