c++-gtk-utils
Public Types | Public Member Functions | List of all members
Cgu::Thread::JoinableHandle Class Reference

A class wrapping a Thread::Thread object representing a joinable thread. More...

#include <c++-gtk-utils/thread.h>

Public Types

enum  Action { detach_on_exit, join_on_exit }
 

Public Member Functions

void cancel ()
 
bool join ()
 
void detach ()
 
bool is_caller ()
 
bool is_managing ()
 
JoinableHandleoperator= (JoinableHandle &&h)
 
 JoinableHandle (std::unique_ptr< Cgu::Thread::Thread > thr, Action act)
 
 JoinableHandle (JoinableHandle &&h)
 
 JoinableHandle ()
 
 ~JoinableHandle ()
 

Detailed Description

A class wrapping a Thread::Thread object representing a joinable thread.

See also
Thread::Thread Thread::Future

This class enables a joinable thread to be made more easily exception safe. It can also be used to provide that a joinable thread is not detached or joined while other methods dependent on that might still be called, and to provide a defined result where there are multiple calls to join() and/or detach(). When it is destroyed, it will either detach or join the thread represented by the wrapped Thread::Thread object unless it has previously been detached or joined using the detach() or join() methods, so avoiding thread resource leaks. Whether it will detach() or join() on destruction depends on the Thread::JoinableHandle::Action argument passed to the Thread::JoinableHandle::JoinableHandle(std::unique_ptr<Thread::Thread>, Action) constructor.

Passing Thread::JoinableHandle::detach_on_exit to that argument is not always the correct choice where the thread callback has been bound to a reference argument in local scope and an exception might be thrown, because the thread will keep running after the Thread::JoinableHandle object and other local variables have (because of the exception) gone out of scope. Consider the following trivial parallelized calculation example:

std::vector<int> get_readings();
void get_mean(const std::vector<int>& v, int& result);
void get_std_deviation(const std::vector<int>& v, int& result); // might throw
void show_result(int mean, int deviation);
using namespace Cgu;
void do_calc() {
int i, j;
std::vector<int> v = get_readings();
std::unique_ptr<Thread::Thread> t =
Thread::Thread::start(std::bind(&get_mean, std::cref(v), std::ref(i)), true);
if (t.get()) { // checks whether thread started correctly
get_std_deviation(v, j);
t->join();
show_result(i, j);
}
}

If get_std_deviation() throws, as well as there being a potential thread resource leak by virtue of no join being made, the thread executing get_mean() will continue running and attempt to access variable v, and put its result in variable i, which may by then both be out of scope. To deal with such a case, the thread could be wrapped in a Thread::JoinableHandle object which joins on exit rather than detaches, for example:

...
using namespace Cgu;
void do_calc() {
int i, j;
std::vector<int> v = get_readings();
Thread::JoinableHandle t{Thread::Thread::start(std::bind(&get_mean, std::cref(v), std::ref(i)), true),
if (t.is_managing()) { // checks whether thread started correctly
get_std_deviation(v, j);
t.join();
show_result(i, j);
}
}

Better still, however, would be to use Cgu::Thread::Future in this kind of usage, namely a usage where a worker thread is intended to provide a result for inspection.

Note
These examples assume that the std::vector library implementation permits concurrent reads of a vector object by different threads. Whether that is the case depends on the documentation of the library concerned (if designed for a multi-threaded environment, most will permit this, and it is required for a fully conforming C++11 library implementation).

Member Enumeration Documentation

◆ Action

Enumerator
detach_on_exit 
join_on_exit 

Constructor & Destructor Documentation

◆ JoinableHandle() [1/3]

Cgu::Thread::JoinableHandle::JoinableHandle ( std::unique_ptr< Cgu::Thread::Thread thr,
Action  act 
)
inline

This constructor initializes a new JoinableHandle object with a std::unique_ptr<Thread::Thread> object, as provided by Thread::Thread::start(). This is a move operation which transfers ownership to the new object.

Parameters
thrThe initializing Thread::Thread object (which must have been created as joinable) passed by a std::unique_ptr smart pointer. This is a move operation.
actEither Thread::JoinableHandle::detach_on_exit (which will cause the destructor to detach the thread if it has not previously been detached or joined) or Thread::JoinableHandle::join_on_exit (which will cause the destructor to join the thread if it has not previously been detached or joined).
Exceptions
Cgu::Thread::MutexErrorThrows this exception if initialization of the internal mutex fails. The constructor is strongly exception safe: if Cgu::Thread::MutexError is thrown, the initializing std::unique_ptr<Cgu::Thread::Thread> object will be left unchanged. (It is often not worth checking for this exception, as it means either memory is exhausted or pthread has run out of other resources to create new mutexes.)
Note
1. It is not necessary to check that the thread parameter represents a correctly started thread (that is, that thr.get() does not return 0) before this constructor is invoked, because that can be done after construction by calling JoinableHandle::is_managing() (a JoinableHangle object can safely handle a case where thr.get() does return 0). This enables a JoinableHandle object to be directly initialized by this constructor from a call to Thread::Thread::start().
2. No synchronization is carried out with respect to the initializing std::unique_ptr object. This is because such an object is usually passed to this constructor as a temporary, which is only visible and accessible in the thread carrying out the move operation, in which case synchronization would represent pointless overhead. In a case where the user uses std::move to force a move from a named std::unique_ptr object, and that named object's lifetime is managed by (or the object is otherwise accessed by) a different thread than the one making the move, the user must carry out her own synchronization with respect to that different thread, as the initializing std::unique_ptr object will be mutated by the move.
See also
JoinableHandle::is_managing().

◆ JoinableHandle() [2/3]

Cgu::Thread::JoinableHandle::JoinableHandle ( JoinableHandle &&  h)
inline

This constructor initializes a new JoinableHandle object with an existing JoinableHandle object. This is a move operation which transfers ownership to the new object.

Parameters
hThe initializing JoinableHandle object, which will cease to hold a valid Thread::Thread object after the initialization has taken place.
Exceptions
Cgu::Thread::MutexErrorThrows this exception if initialization of the internal mutex fails. The constructor is strongly exception safe: if Cgu::Thread::MutexError is thrown, the initializing Cgu::Thread::JoinableHandle object will be left unchanged. (It is often not worth checking for this exception, as it means either memory is exhausted or pthread has run out of other resources to create new mutexes.)
Note
No synchronization is carried out with respect to the initializing rvalue. This is because temporaries are only visible and accessible in the thread carrying out the move operation and synchronization for them would represent pointless overhead. In a case where a user uses std::move to force a move from a named object, and that named object's lifetime is managed by (or the object is otherwise accessed by) a different thread than the one making the move, the user must carry out her own synchronization with respect to that different thread, as the named object will be mutated by the move.

◆ JoinableHandle() [3/3]

Cgu::Thread::JoinableHandle::JoinableHandle ( )
inline

The default constructor. Nothing is managed until the move assignment operator has been called.

Exceptions
Cgu::Thread::MutexErrorThrows this exception if initialization of the internal mutex fails. (It is often not worth checking for this exception, as it means either memory is exhausted or pthread has run out of other resources to create new mutexes.)

Since 2.0.8

◆ ~JoinableHandle()

Cgu::Thread::JoinableHandle::~JoinableHandle ( )

The destructor will detach a managed thread (if the Thread::JoinableHandle::detach_on_exit flag is set) or join it (if the Thread::JoinableHandle::join_on_exit flag is set), unless it has previously been detached or joined with the detach() or join() methods. The destructor is thread safe (any thread may destroy the JoinableHandle object). The destructor will not throw.

Member Function Documentation

◆ cancel()

void Cgu::Thread::JoinableHandle::cancel ( )

Cancels the thread represented by the wrapped Thread::Thread object. It can be called by any thread. The effect is undefined if when called the thread represented by the wrapped Thread::Thread object has both (a) already terminated and (b) had a call to join() or detach() made for it. Accordingly, if the user is not able to establish from the program logic whether the thread has terminated, cancel() must not be called after a call to detach() has been made or a call to join() has returned: this can be ensured by only detaching or joining via this object's destructor (that is, by not using the explicit detach() and join() methods). This method does not throw.

Note
Use this method with care - see Thread::cancel() for further information.

◆ detach()

void Cgu::Thread::JoinableHandle::detach ( )

Detaches the thread represented by this Thread::Thread object, so as to make it unjoinable, unless the detach() or join() method has previously been called in which case this call does nothing. It does not throw.

◆ is_caller()

bool Cgu::Thread::JoinableHandle::is_caller ( )

Specifies whether the calling thread is the same thread as is represented by the wrapped Thread::Thread object. It can be called by any thread. The effect is undefined if the thread represented by the wrapped Thread::Thread object has both (a) already terminated and (b) had a call to join() or detach() made for it. Accordingly, if the user is not able to establish from the program logic whether the thread has terminated, is_caller() must not be called after a call to detach() has been made or a call to join() has returned: this can be ensured by only detaching or joining via this object's destructor (that is, by not using the explicit detach() and join() methods). This method does not throw.

Returns
Returns true if the caller is in the thread represented by the wrapped Thread::Thread object. If not, or this JoinableHandle does not wrap any Thread object, then returns false.

◆ is_managing()

bool Cgu::Thread::JoinableHandle::is_managing ( )

Specifies whether this JoinableHandle object has been initialized with a Thread::Thread object representing a correctly started thread in respect of which neither JoinableHandle::detach() nor JoinableHandle::join() has been called. It can be called by any thread. It is principally intended to enable the constructor taking a std::unique_ptr<Cgu::Thread::Thread> object to be directly initialized by a call to Thread::Thread::start(), by providing a means for the thread calling Thread::Thread::start() to check afterwards that the new thread did, in fact, start correctly. Note that this method will return true even after the thread has finished, provided neither the join() nor detach() method has been called.

Returns
Returns true if this object has been initialized by a Thread::Thread object representing a correctly started thread in respect of which neither JoinableHandle::detach() nor JoinableHandle::join() has been called, otherwise false.

◆ join()

bool Cgu::Thread::JoinableHandle::join ( )

Joins the thread represented by the wrapped Thread::Thread object (that is, waits for it to terminate), unless the detach() or join() method has previously been called in which case this call does nothing. It can be called by any thread other than the one represented by the wrapped Thread::Thread object, but only one thread can wait on it: if one thread (thread A) calls it while another thread (thread B) is already blocking on it, thread A's call to this method will return immediately and return false. It does not throw.

Returns
true if a successful join() has been accomplished (that is, detach() or join() have not previously been called), otherwise false.

◆ operator=()

JoinableHandle& Cgu::Thread::JoinableHandle::operator= ( JoinableHandle &&  h)

Moves one JoinableHandle object to another JoinableHandle object. This is a move operation which transfers ownership to the assignee, as the handles store their Thread::Thread object by std::unique_ptr<>. Any existing thread managed by the assignee prior to the move will be detached if it has not already been detached or joined. This method will not throw.

Parameters
hThe assignor/movant, which will cease to hold a valid Thread::Thread object after the move has taken place.
Returns
A reference to the assignee JoinableHandle object after assignment.
Note
This method is thread safe as regards the assignee (the object assigned to), but no synchronization is carried out with respect to the rvalue assignor/movant. This is because temporaries are only visible and accessible in the thread carrying out the move operation and synchronization for them would represent pointless overhead. In a case where the user uses std::move to force a move from a named object, and that named object's lifetime is managed by (or the object is otherwise accessed by) a different thread than the one making the move, the user must carry out her own synchronization with respect to that different thread, as the named object will be mutated by the move.

The documentation for this class was generated from the following file:
Cgu
Definition: application.h:44
Cgu::Thread::JoinableHandle::join_on_exit
@ join_on_exit
Definition: thread.h:474
Cgu::Thread::Thread::join
void join() noexcept
Definition: thread.h:227
Cgu::Thread::Thread::start
static std::unique_ptr< Cgu::Thread::Thread > start(const Cgu::Callback::Callback *cb, bool joinable)