Thread overview | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
January 23, 2014 TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
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? |
January 23, 2014 Re: TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Frustrated | 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.
|
January 23, 2014 Re: TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dicebot | 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).
|
January 23, 2014 Re: TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Frustrated | 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. |
January 23, 2014 Re: TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Frustrated | 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
|
January 24, 2014 Re: TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Frustrated | 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?
|
January 24, 2014 Re: TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to dennis luehring | 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.
|
January 24, 2014 Re: TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Stanislav Blinov | 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) |
January 24, 2014 Re: TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dicebot | 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. |
January 24, 2014 Re: TLF = thread local functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Stanislav Blinov | 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". |
Copyright © 1999-2021 by the D Language Foundation