未验证 提交 e8e0d103 编写于 作者: A Arthur O'Dwyer 提交者: GitHub

CP.1: Simplify example, show good example, expand on rationale (#1615)

"Make your code thread-safe" usually means "don't use global state."
Advice to replace global state with `thread_local` state is usually misguided.
https://quuxplusone.github.io/blog/2018/11/14/fiber-local-storage/
上级 19f2672b
......@@ -13948,29 +13948,52 @@ However, over time, code fragments can turn up in unexpected places.
double cached_computation(double x)
{
// bad: these two statics cause data races in multi-threaded usage
// bad: these statics cause data races in multi-threaded usage
static double cached_x = 0.0;
static double cached_result = COMPUTATION_OF_ZERO;
double result;
if (cached_x == x)
return cached_result;
result = computation(x);
cached_x = x;
cached_result = result;
return result;
if (cached_x != x) {
cached_x = x;
cached_result = computation(x);
}
return cached_result;
}
Although `cached_computation` works perfectly in a single-threaded environment, in a multi-threaded environment the two `static` variables result in data races and thus undefined behavior.
There are several ways that this example could be made safe for a multi-threaded environment:
##### Example, good
struct ComputationCache {
double cached_x = 0.0;
double cached_result = COMPUTATION_OF_ZERO;
double compute(double x) {
if (cached_x != x) {
cached_x = x;
cached_result = computation(x);
}
return cached_result;
}
};
Here the cache is stored as member data of a `ComputationCache` object, rather than as shared static state.
This refactoring essentially delegates the concern upward to the caller: a single-threaded program
might still choose to have one global `ComputationCache`, while a multi-threaded program might
have one `ComputationCache` instance per thread, or one per "context" for any definition of "context."
The refactored function no longer attempts to manage the allocation of `cached_x`. In that sense,
this is an application of the Single Responsibility Principle.
In this specific example, refactoring for thread-safety also improved reusability in single-threaded
programs. It's not hard to imagine that a single-threaded program might want two `ComputationCache` instances
for use in different parts of the program, without having them overwrite each other's cached data.
There are several other ways one might add thread-safety to code written for a standard multi-threaded environment
(that is, one where the only form of concurrency is `std::thread`):
* Delegate concurrency concerns upwards to the caller.
* Mark the `static` variables as `thread_local` (which might make caching less effective).
* Implement concurrency control, for example, protecting the two `static` variables with a `static` lock (which might reduce performance).
* Have the caller provide the memory to be used for the cache, thereby delegating both memory allocation and concurrency concerns upwards to the caller.
* Mark the state variables as `thread_local` instead of `static`.
* Implement concurrency control, for example, protecting access to the two `static` variables with a `static std::mutex`.
* Refuse to build and/or run in a multi-threaded environment.
* Provide two implementations, one which is used in single-threaded environments and another which is used in multi-threaded environments.
* Provide two implementations: one for single-threaded environments and another for multi-threaded environments.
##### Exception
......
......@@ -97,6 +97,7 @@ completers
componentization
composability
composable
ComputationCache
conceptsTS
cond
const
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册