www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - TLF = thread local functions

reply "Frustrated" <c1514843 drdrb.com> writes:
So, TLS solves the data issue with threading. I just thought,
with out much thinking, what about having thread local functions?
Doesn't make sense? Let me explain.

Functions generally are not thread safe because of reentry,
right? The same data is used by the function for each thread
calling it and sense threads could effectively call a function
while the same function is being executed by another thread, the
data is correct.

To solve this, why not "parameterize" functions based on threads.

Essentially:

void funcThread1(...) { ... }

void funcThread2(...) { exactly the same code as above }

funcThread1 is only ever called on thread 1 and funcThread2 is
only ever called on thread 2. There is no issues of threading as
these functions are essentially thread local.

We can also combine them into one function:

// set stack based on threadidx before call. (for n threads there
are n stacks for this function)
void funcThread(...) { ... }

in this case, the compiler can simply set the stack based on the
thread id.

Should this not solve issues with functions in threading in a
similar way that TLS works? (it's just making the stack thread
local and functions are just code and data, the data part being
the issue)

While suck code might not work in all scenarios(such as in
general parallelization) it would make code for threading much
simpler to write(no locks) in many cases. As far as I can see,
the only real issue is that the stack is not thread local for
functions and hence acts as a global variable for functions,
which is the problem?

Is this possible?
Jan 23 2014
next sibling parent reply "Dicebot" <public dicebot.lv> writes:
On Thursday, 23 January 2014 at 14:44:01 UTC, Frustrated wrote:
 Functions generally are not thread safe because of reentry,
 right?
No. They are not thread safe because they use shared data (explicitly/implicitly). Functions that only use thread-local data are always thread-safe.
Jan 23 2014
parent reply "Frustrated" <c1514843 drdrb.com> writes:
On Thursday, 23 January 2014 at 14:49:11 UTC, Dicebot wrote:
 On Thursday, 23 January 2014 at 14:44:01 UTC, Frustrated wrote:
 Functions generally are not thread safe because of reentry,
 right?
No. They are not thread safe because they use shared data (explicitly/implicitly). Functions that only use thread-local data are always thread-safe.
Um, duh, but in d data is already TLS. The point is that making the **STACK** TLS too should a way around having to use synchronization. Precisely because the STACK is not TLS makes functions not thread safe(since data is already "safe" in d). A strongly pure thread local function would never have any threading issues what so ever. Hence, no synchronization would be required and they would be very fast(just an extra instruction or two to fix up the stack if the thread id can be quickly known).
Jan 23 2014
next sibling parent "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Thursday, 23 January 2014 at 16:15:46 UTC, Frustrated wrote:
 On Thursday, 23 January 2014 at 14:49:11 UTC, Dicebot wrote:
 On Thursday, 23 January 2014 at 14:44:01 UTC, Frustrated wrote:
 Precisely because the STACK is not TLS makes functions not 
 thread
 safe(since data is already "safe" in d).
Well, with a nasty hack you sort of can make a copy of stack for a delegate, but even then: pointers and references live on the stack too, yet they are aliases.
Jan 23 2014
prev sibling parent "David Nadlinger" <code klickverbot.at> writes:
On Thursday, 23 January 2014 at 16:15:46 UTC, Frustrated wrote:
 The point is that making the **STACK** TLS too should a way
 around having to use synchronization.

 Precisely because the STACK is not TLS makes functions not 
 thread
 safe(since data is already "safe" in d).
Maybe you could elaborate a bit on where you see the problem here? The claim that stack memory is not thread-local contradicts commonly used terminology, since the execution stack is intrinsically part of a single thread. In fact, from a user-space perspective a context switch is little more than just switching out the CPU registers, including the stack pointer, for a different set. David
Jan 23 2014
prev sibling parent reply dennis luehring <dl.soluz gmx.net> writes:
Am 23.01.2014 15:44, schrieb Frustrated:
 So, TLS solves the data issue with threading. I just thought,
 with out much thinking, what about having thread local functions?
 Doesn't make sense? Let me explain.

 Functions generally are not thread safe because of reentry,
 right? The same data is used by the function for each thread
 calling it and sense threads could effectively call a function
 while the same function is being executed by another thread, the
 data is correct.
no - the parameters and local vars of the function are in the stack of the thread - so there is no problem with them, only shared variables can have a need to synchronization your idea tries to solve non existing problems?
Jan 23 2014
parent reply "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Friday, 24 January 2014 at 06:03:27 UTC, dennis luehring wrote:

 no - the parameters and local vars of the function are in the 
 stack of the thread - so there is no problem with them, only 
 shared variables can have a need to synchronization

 your idea tries to solve non existing problems?
...Unless the thread is started with a delegate (literal or member function), which implicitly gains an unsynchronized view of its enclosing scope (or class). Granted, the "default" D's spawning function, std.concurrency.spawn, being restrictive like a firm parent, simply would not allow this. However, it may sometimes be feasible to do so (using Thread interface directly), although this is a case for "I know what I'm doing" category. If we had a way of explicitly capturing variables for delegate literals, the problem with delegates could go away altogether.
Jan 24 2014
parent reply "Dicebot" <public dicebot.lv> writes:
On Friday, 24 January 2014 at 08:11:53 UTC, Stanislav Blinov 
wrote:
 ...Unless the thread is started with a delegate (literal or 
 member function), which implicitly gains an unsynchronized view 
 of its enclosing scope (or class).
Which means accessing shared data pretty much by definition. It actually should not even compile without explicit casts. I guess yet another delegate qualifier bug.
 Um, duh, but in d data is already TLS.
1) not necessarily, is is only default 2) "using TLS data" usually implies "using _own_ TLS data" as you shouldn't be able to get reference to TLS of other thread without dirty hacks (it breaks basic type system assumptions)
Jan 24 2014
parent reply "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Friday, 24 January 2014 at 09:50:44 UTC, Dicebot wrote:
 On Friday, 24 January 2014 at 08:11:53 UTC, Stanislav Blinov 
 wrote:
 ...Unless the thread is started with a delegate (literal or 
 member function), which implicitly gains an unsynchronized 
 view of its enclosing scope (or class).
Which means accessing shared data pretty much by definition.
I would rephrase that as "implicitly sharing not explicitly shared data".
 It actually should not even compile without explicit casts. I 
 guess yet another delegate qualifier bug.
Why shouldn't it compile? Safe spawner (std.concurrency.spawn) does not accept delegates. Unsafe one (core.thread.Thread) does. I wouldn't consider it a bug since in the context of one thread it's pretty much a feature :) But I agree that some special syntax for threading and otherwise restricting sharing would be great. Maybe another set of parentheses? Consider: // Call could be anything. Just a call, do-some-work-and call, // spawn a thread, etc. void call(F,Args...)(F dg,Args args) if (is(F == delegate)) { dg(args); } void main() { int myPreciousInt = 42; string myPreciousString = "precious"; // current syntax, horrifying if call spawns a thread with that delegate call({ myPreciousInt = 151; // modifies main's myPreciousInt myPreciousString = "stolen"; // ditto }); // current syntax with parameters, ditto call((int i){ myPreciousInt = i; myPreciousString = "stolen"; }); // explicit capture syntax: call((myPreciousInt){ // capture by value myPreciousInt = 132; // changes local variable myPreciousString = "stolen"; // would not compile, variable is not captured }); // explicit capture with parameters: call((ref myPreciousInt)(int i){ // capture by reference myPreciousInt = 144; // will modify main's myPreciousInt too }); } This could be extended to support various capture qualifies: ref - by reference in - by move ref shared - by reference if variable is shared etc. Looks like C++'s [](){}, perhaps, but with D's powerful compile-time facilities we could do so much more. For example, the above, coupled with some introspection with e.g. __traits(captures, dg) could give way to implementing safe thread spawners even for delegates: e.g. disallow capturing non-shared data by reference. As I mentioned, currently pretty much all that can be done is a shallow copy of the stack (which means allocation), and I don't even know how portable or safe that is :)
 Um, duh, but in d data is already TLS.
1) not necessarily, is is only default 2) "using TLS data" usually implies "using _own_ TLS data" as you shouldn't be able to get reference to TLS of other thread without dirty hacks (it breaks basic type system assumptions)
In my understanding delegates are pretty much unique at that. Probably because they were redesigned like that since D1, but never were taken one step further towards multithreading.
Jan 24 2014
parent reply "Dicebot" <public dicebot.lv> writes:
On Friday, 24 January 2014 at 10:43:05 UTC, Stanislav Blinov 
wrote:
 It actually should not even compile without explicit casts. I 
 guess yet another delegate qualifier bug.
Why shouldn't it compile? Safe spawner (std.concurrency.spawn) does not accept delegates. Unsafe one (core.thread.Thread) does. I wouldn't consider it a bug since in the context of one thread it's pretty much a feature :) But I agree that some special syntax for threading and otherwise restricting sharing would be great. Maybe another set of parentheses?
D type system is supposed to guarantee that you never ever can get pointer/reference to data that is not stored in your own TLS (including stack) unless it is marked as shared (immutable / __gshared are in shared category). By allowing to spawn thread with a delegate which has context pointer not qualified as shared you break that type system sanity rule. Multithreading / concurrency in D is quite different from C++.
 Um, duh, but in d data is already TLS.
1) not necessarily, is is only default 2) "using TLS data" usually implies "using _own_ TLS data" as you shouldn't be able to get reference to TLS of other thread without dirty hacks (it breaks basic type system assumptions)
In my understanding delegates are pretty much unique at that. Probably because they were redesigned like that since D1, but never were taken one step further towards multithreading.
Delegates unique in a sense that they often erase type qualifiers for the context because of rather hacky current implementation. All such cases are bugs that contradict language spec, there is nothing intentional about it. There are no legal exceptions to the rule "all shared data must be shared".
Jan 24 2014
parent "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Friday, 24 January 2014 at 11:00:42 UTC, Dicebot wrote:

 D type system is supposed to guarantee that you never ever can 
 get pointer/reference to data that is not stored in your own 
 TLS (including stack) unless it is marked as shared (immutable 
 / __gshared are in shared category).
Agreed.
 By allowing to spawn thread with a delegate which has context 
 pointer not qualified as shared you break that type system 
 sanity rule.
Agreed again. AFAIK, that was the exact reasoning behind restricting std.concurrency to not allow delegates as thread functions or message types.
 Multithreading / concurrency in D is quite different from C++.
With this I cannot completely agree. Granted, we have "shared" qualifier and by-default-TLS static data. Oh, and built-in monitors + synchronized methods (which are of dubious value at best). In that, D differs. But the bare bones of concurrency still are the same. Access to shared data still has to be manually synchronized somehow (locks, atomics, cas, you name it). Threads still have to be spawned and joined as necessary. Ok, we have some of the boilerplate removed thanks to std.concurrency and std.parallelism, but that's just library solutions, not unlike those that exist for C++. I.e. there's nothing in the language that says "use that!". And sadly, when threading is concerned, purity doesn't help much because of the "weak purity" concept.
 Delegates unique in a sense that they often erase type 
 qualifiers for the context because of rather hacky current 
 implementation. All such cases are bugs that contradict 
 language spec, there is nothing intentional about it.

 There are no legal exceptions to the rule "all shared data must 
 be shared".
Agreed. Oh, speaking of holes in the type system. Synchronization primitives like Mutex, Condition, etc. (which should be de-facto shared) aren't qualified as shared. Do you know if it's intentional or?..
Jan 24 2014