55 lines
2.3 KiB
Plaintext
55 lines
2.3 KiB
Plaintext
== Why is this an issue?
|
|
|
|
In contrast to normal functions, coroutines can suspend and later resume their execution. Depending on the program, the coroutine may resume on a different thread of execution than the one it was started or run previously on.
|
|
|
|
Therefore, the access to the "same" variable with `thread_local` storage may produce different values, as illustrated below:
|
|
[source,cpp]
|
|
----
|
|
thread_local std::vector<Decorator> decorators;
|
|
lazy<Thingy> doSomething() {
|
|
// evaluation started on thread t1
|
|
/* .... */
|
|
const std::size_t decoratorCount = decorators.size(); // value specific to thread t1
|
|
auto result = co_await produceThingy();
|
|
// after co_await, execution resumes on thread t2
|
|
for (std::size_t i = 0; i < decoratorCount; ++i) {
|
|
decorators[i].modify(result); // access value specific to t2
|
|
// miss some tasks if t1:decorators.size() < t2:decorators.size()
|
|
// undefined behavior if t1:decorators.size() > t2:decorators.size()
|
|
}
|
|
co_return result;
|
|
}
|
|
----
|
|
This behavior is surprising and unintuitive compared to normal functions that are always evaluated on a single thread.
|
|
The same issue can happen for the use of different `thread_local` variables if their values are interconnected (e.g., one is the address of the buffer, and the other is the number of elements in the buffer).
|
|
|
|
Moreover, access to `thread_local` variables defined inside the coroutine may read uninitialized memory.
|
|
Each such variable is initialized when a specific thread enters the function for the first time,
|
|
and if the function was never called from a thread on which the coroutine is resumed, it is uninitialized.
|
|
|
|
This rule raises an issue on the declaration of `thread_local` variables and access to `thread_local` variables
|
|
in coroutines.
|
|
|
|
=== Noncompliant code example
|
|
|
|
[source,cpp]
|
|
----
|
|
thread_local std::vector<Decorator> decorators;
|
|
lazy<Thingy> doSomething() {
|
|
thread_local Decorator localDecorator; // Noncompliant
|
|
const std::size_t decoratorCount = decorators.size(); // Noncompliant
|
|
/* ... */
|
|
auto result = co_await produceThingy();
|
|
for (std::size_t i = 0; i < taskCount; ++i) {
|
|
decorators[i].modify(result);
|
|
}
|
|
localDecorator.modify(result); // Noncompliant
|
|
co_return result;
|
|
}
|
|
----
|
|
|
|
== Resources
|
|
|
|
* {cpp} reference - https://en.cppreference.com/w/cpp/language/storage_duration[Storage class specifiers: thread_local]
|
|
|