Jump to page: 1 2
Thread overview
TLF = thread local functions
Jan 23, 2014
Frustrated
Jan 23, 2014
Dicebot
Jan 23, 2014
Frustrated
Jan 23, 2014
Stanislav Blinov
Jan 23, 2014
David Nadlinger
Jan 24, 2014
dennis luehring
Jan 24, 2014
Stanislav Blinov
Jan 24, 2014
Dicebot
Jan 24, 2014
Stanislav Blinov
Jan 24, 2014
Dicebot
Jan 24, 2014
Stanislav Blinov
January 23, 2014
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
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
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
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
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
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
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
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
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
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".
« First   ‹ Prev
1 2