Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upInteraction with thread local storage #2
Comments
This comment has been minimized.
This comment has been minimized.
|
P0057 Coroutines do not represent independent threads of execution. When a coroutine is executing, it gets the same view of the thread-local storage as whomever called or resumed the coroutine. For example,
whenever you pull from the generator. it will print the value from the thread that resumed the coroutine (pulled from the generator in this case). I will check with Core Language group if they would like to see a non-normative note with this clarification. |
GorNishanov
closed this
Jun 29, 2016
This comment has been minimized.
This comment has been minimized.
|
What happens when a coroutine is migrated between threads by the scheduler? When the coroutine is resumed in a different thread, does it see the thread local variables of the thread it was moved from, or the ones it was resumed in? |
This comment has been minimized.
This comment has been minimized.
|
Coroutine initial call or resumption call are regular function calls that do not involve any thread switching, therefore, you always get the thread-local storage of the current thread. If you are thinking of fibers or boost::coroutines, that is a different story. |
This comment has been minimized.
This comment has been minimized.
I was indeed thinking of these, sorry for the confusion.
I guess I was missing this. So IIUC:
|
This comment has been minimized.
This comment has been minimized.
Not exactly, at least not with std::future or std::future in concurrency TS. The future::get() is a boring blocking call that does not donate its thread to a coroutine. It just blocks the current thread waiting for a signal that coroutine runs to completion and produced a result or an exception.
Before await, it will be executing on a thread that called foo().
s/thread that calls .get()/ thread that resumes the suspended coroutine/, then, yes. You always getting the thread local storage of the current thread. |
This comment has been minimized.
This comment has been minimized.
|
Wait (no pun intended!), so if before and after I haven't seen much written about the requirements on the "environment scheduler" in the papers (but maybe I missed some). Consider the following code: future<void> foo() {
thread_local auto tls = 314;
for (int i = 0; i < 10; ++i) {
cout << tls << std::endl;
co_await SomeAsyncApi();
}
}On the thread that this function is initialized the From:
it seems to me that whether UB occurs depends on the platform's scheduler. Is this so? |
This comment has been minimized.
This comment has been minimized.
|
Correct. |
This comment has been minimized.
This comment has been minimized.
|
Unless extra guarantees are provided by the scheduler [*] I think it will be very hard to reason about what is going on in a coroutine that uses or references Sometimes it is desired to access a future<void> ohno() {
1.0 / 0.0;
co_await SomeAsyncAPI();
if (errno == 0) { cout << "I can divide by zero!" << endl; }
}The variable (And no, checking Another issue could be when silently using Another thing that I worry is, what optimizations the compiler can do in the presence of thread locals between coroutines? In my previous comment I had an example with the thread_local auto tls;
future<void> foo() {
for (int i = 0; i < 10; ++i) {
cout << tls << std::endl; // A
co_await SomeAsyncApi();
cout << tls << std::endl; // B
}
}Can the compiler generate code for [*] Something I would be opposed to. The scheduler should be free to move coroutines around as it deems fit. |
This comment has been minimized.
This comment has been minimized.
Compiler won't cache the addresses of a TLS across the suspend point as it will violate the "you get the thread-local of the currently running thread" behavior.
I agree. Note that P0057 gives you mechanical function to state machine transformation, library writer imbues it with meaning. How you want to use thread-local should be looked at in the context of semantics of the library layer utilizing the coroutines. |
This comment has been minimized.
This comment has been minimized.
|
@GorNishanov Thanks for the explanations, really appreciated. |
This comment has been minimized.
This comment has been minimized.
|
@GorNishanov reading through the LLVM RFC I do not find any mention about the caching (or lack thereof) of TLS variables across calls to |
This comment has been minimized.
This comment has been minimized.
|
@gnzlbg In LLVM thread_locals are modelled as global variables (even if thread_local is a local variable in a function). A call to coro.save and coro.suspend intrinsics (from LLVM perspective) can read or write any memory, (just like any other function call), thus, LLVM is not free to cache any read from a global variable across suspend point (including thread_local globals). So, no special handling of thread_local is required, therefore, not mentioned. Though, I am thinking of adding Q&A at the end of docs/Coroutines.rst. I can include thread_local discussion there. |
This comment has been minimized.
This comment has been minimized.
|
I see, thanks!
That would be very helpful. It might be good to also mention any thought that has been given to dynamically-sized types in coroutines and sketch how one could extend the proposal to allow these in the future. |
gnzlbg commentedJun 29, 2016
I cannot find neither in the wording nor the papers any mention of the interactions of coroutines with thread-local storage. Is it mentioned somewhere?