@ -12949,26 +12946,25 @@ If a `thread` joins, we can safely pass pointers to objects in the scope of the
void some_fct(int* p)
{
int x = 77;
raii_thread t0(f, &x); // OK
raii_thread t1(f, p); // OK
raii_thread t2(f, &glob); // OK
joining_thread t0(f, &x); // OK
joining_thread t1(f, p); // OK
joining_thread t2(f, &glob); // OK
auto q = make_unique<int>(99);
raii_thread t3(f, q.get()); // OK
joining_thread t3(f, q.get()); // OK
// ...
}
An `raii_thread` is a `std::thread` with a destructor that joined and cannot be `detached()`.
A`gsl::joining_thread` is a `std::thread` with a destructor that joined and cannot be `detached()`.
By "OK" we mean that the object will be in scope ("live") for as long as a `thread` can use the pointer to it.
The fact that `thread`s run concurrently doesn't affect the lifetime or ownership issues here;
these `thread`s can be seen as just a function object called from `some_fct`.
##### Enforcement
Ensure that `raii_thread`s don't `detach()`.
Ensure that `joining_thread`s don't `detach()`.
After that, the usual lifetime and ownership (for local objects) enforcement applies.
### <aname="Rconc-detach"></a>CP.24: Think of a detached `thread` as a global container
### <aname="Rconc-detach"></a>CP.24: Think of a `thread` as a global container
##### Reason
@ -13013,60 +13009,64 @@ Even objects with static storage duration can be problematic if used from detach
thread continues until the end of the program, it might be running concurrently with the destruction
of objects with static storage duration, and thus accesses to such objects might race.
##### Enforcement
##### Note
This rule is redundant if you [don't `detach()`](#Rconc-detached_thread) and [use `gsl::joining_tread`](#Rconc-joining_thread).
However, converting code to follow those guidelines could be difficult and even impossible for third-party libraries.
In such cases, the rule becomes essential for lifetime safety and type safety.
In general, it is undecidable whether a `detach()` is executed for a `thread`, but simple common cases are easily detected.
If we cannot prove that a `thread` does not `detach()`, we must assume that it does and that it outlives the scope in which it was constructed;
After that, the usual lifetime and ownership (for global objects) enforcement applies.
##### Enforcement
Flag attempts to pass local variables to a thread that might `detach()`.
### <aname="Rconc-raii_thread"></a>CP.25: Prefer `gsl::raii_thread` over `std::thread` unless you plan to `detach()`
### <aname="Rconc-joining_thread"></a>CP.25: Prefer `gsl::joining_thread` over `std::thread`
##### Reason
An `raii_thread` is a thread that joins at the end of its scope.
A `joining_thread` is a thread that joins at the end of its scope.
Detached threads are hard to monitor.
It is harder to ensure absence of errors in detached threads (and potentially detached threads)
??? Place all "immortal threads" on the free store rather than `detach()`?
##### Example
???
##### Enforcement
???
##### Example, bad
### <aname="Rconc-detached_thread"></a>CP.26: Prefer `gsl::detached_thread` over `std::thread` if you plan to `detach()`
void f() { std::cout << "Hello "; }
##### Reason
struct F {
void operator()() { std::cout << "parallel world "; }
};
Often, the need to `detach` is inherent in the `thread`s task.
Documenting that aids comprehension and helps static analysis.
int main()
{
std::thread t1{f}; // f() executes in separate thread
std::thread t2{F()}; // F()() executes in separate thread
} // spot the bugs
##### Example
void heartbeat();
void use()
{
gsl::detached_thread t1(heartbeat); // obviously need not be joined
std::thread t2(heartbeat); // do we need to join? (read the code for heartbeat())
// ...
}
void f() { std::cout << "Hello "; }
Flag unconditional `detach` on a plain `thread`
struct F {
void operator()() { std::cout << "parallel world "; }
};
int main()
{
std::thread t1{f}; // f() executes in separate thread
std::thread t2{F()}; // F()() executes in separate thread
### <aname="Rconc-thread"></a>CP.27: Use plain `std::thread` for `thread`s that detach based on a run-time condition (only)
t1.join();
t2.join();
} // one bad bug left
##### Reason
`thread`s that are supposed to unconditionally `join` or unconditionally `detach` can be clearly identified as such.
The plain `thread`s should be assumed to use the full generality of `std::thread`.
##### Example, bad
##### Example
The code determining whether to `join()` or `detach()` may be complicated and even decided in the thread of functions called from it or functions called by the function that creates a thread:
void tricky(thread* t, int n)
{
@ -13083,83 +13083,76 @@ The plain `thread`s should be assumed to use the full generality of `std::thread
// ... should I join here? ...
}
##### Enforcement
This seriously complicted lifetime analysis, and in not too unlikely cases make lifetime analysis impossible.
This implies that we cannot safely refer to local objects in `use()` from the thread or refer to local objects in the thread from `use()`.
???
##### Note
Make "immortal threads" globals, put them in an enclosing scope, or put them on the on the free store rather than `detach()`.
[don't `detach`](#Rconc-detached_thread).
##### Note
### <aname="Rconc-join-undetached"></a>CP.28: Remember to join scoped `thread`s that are not `detach()`ed
Because of old code and third party libraries using `std::thread` this rule can be hard to introduce.
##### Reason
##### Enforcement
A `thread` that has not been `detach()`ed when it is destroyed terminates the program.
Flag uses of 'std::thread':
##### Example, bad
* Suggest use of `gsl::joining_thread`.
* Suggest ["exporting ownership"](#Rconc-detached_thread) to an enclosing scope if it detaches.
* Seriously warn if it is not obvious whether if joins of detaches.
void f() { std::cout << "Hello "; }
### <aname="Rconc-detached_thread"></a>CP.26: Don't `detach()` a thread
struct F {
void operator()() { std::cout << "parallel world "; }
};
##### Reason
int main()
{
std::thread t1{f}; // f() executes in separate thread
std::thread t2{F()}; // F()() executes in separate thread
} // spot the bugs
Often, the need to outlive the scope of its creation is inherent in the `thread`s task,
but implementing that idea by `detach` makes it harder monitor and communicat with the detached thread.
In particular, it is harder (though not impossible) to ensure that the thread completed as expected or lived for as long as expected.
##### Example
void f() { std::cout << "Hello "; }
struct F {
void operator()() { std::cout << "parallel world "; }
};
void heartbeat();
int main()
void use()
{
std::thread t1{f}; // f() executes in separate thread
std::thread t2{F()}; // F()() executes in separate thread
t1.join();
t2.join();
} // one bad bug left
std::thread t(heartbeat); // don't join; heartbeat is meant to run forever
t.detach();
// ...
}
??? Is `cout` synchronized?
This is a reasonable use of a thread, for which `detach()` is commonly used.
There are problems, though.
How do we monitor the detached thread to see if it is alive?
Something might go wrong with the heartbeat, and loosing a haertbeat can be very serious in a system for which it is needed.
So, we need to communicate with the haertbeat thread
(e.g., through a stream of messages or notification events using a `conrition_variable`).
##### Enforcement
An alternative, and usually superior solution is to control its lifetime by placing it in a scope outside its point of creation (or activation).
For example:
* Flag `join`s for `raii_thread`s ???
* Flag `detach`s for `detached_thread`s
void heartbeat();
gsl::joining_thread t(heartbeat); // heartbeat is meant to run "forever"
### <aname="RRconc-pass"></a>CP.30: Do not pass pointers to local variables to non-`raii_thread`s
This heartbeat will (barring error, hardware problems, etc.) run for as long as the program does.
##### Reason
Sometimes, we need to separate the point of creation from the point of ownership:
In general, you cannot know whether a non-`raii_thread` will outlive the scope of the variables, so that those pointers will become invalid.