Threads in C++ ISO_11Draft Date: 20 MAR 2016BackgroundThese notes assume that you have taken and passed COP4610, Operating Systems, and as a consequence have general background knowledge of these concepts:
C++ Thread LibraryThread support has come late to C++, and it is not hard to see why. Unlike Java, where execution threads run on a virtual (software) machine, the entire design philosophy of C++ is that performance should not be sacrificed to functionality. Particularly in low-level entities such as threads, C++ would not want to build a layer of indirection between the thread execution environment and the hardware as made available to the operating system. On the other hand, operating systems provide their own support for threading, the best and most well-known example being posix threads. The task for C++ is to design a thread support system that is as efficient as the native OS-provided library on the one hand but on the other hand is portable from one OS to another. This is not an easy mandate. The C++ thread support library is new with C++11 and is still evolving, with new components being added and in at least one case a component undergoing changes from one version to the next. What follows here is a very abbreviated introduction to this system. The components are shown in the following table: Intro VideosBo Qian (an Apple guy with a nack for clarity, friendliness, and good humor) has made some terrific videos introducing C++ threading:
As you watch these it may be helpful to have this document handy to click on the various entities as they are used. Of course it also helps to follow along with the code and compile/run on linprog. The following command-line compile macro is also handy: #!/bin/sh # # c4610: a command line compile macro for seld-contained C++ thread programs # # usage: "c4610 $1" without .cpp suffix results in executable $1.x # echo "g++ -pthread -std=c++11 -Wall -Wextra -o./$1.x -I. $1.cpp" g++ -pthread -std=c++11 -Wall -Wextra -o./$1.x -I. $1.cpp Just create the file and make it executable. Put it in ~/.bin for ubiquitous access.
We'll take a look at a few of these components and illustrate their use (and the concept of RAII as it applies to threads). A comprehensive study is far beyond what we can accomplish. But with the library at hand, the Internet available, and a small startup experience, you can become as expert as you wish to be. What is RAII?Resource Allocation Is Initialization [RAII] is the term invented by Stroustrup to describe the idea that resource de-allocation can and should be handled by class destructors, so that resource allocations are automatically de-allocated and otherwise cleaned up when an object goes out of scope. This is expecially handy in situations where there may be "side exits" caused by such things as exception throws. The use of RAII should be very familiar to students in the context of dynamic memory allocation: container classes such as List, Vector, Deque, Set, Map all have destructors that de-allocate memory when they are called, which is an automatic call as an object's scope of existence comes to an end. RAII applies in multithreading in the area of lock management (among other places): template < class Mutex > class Locker { private: Mutex& mu_; public: Locker (Mutex m) : mu_(m) { mu_.lock(); } ~Locker () { mu_.unlock(); } }; This captures the essence of the idea. In client code such as this fragment: { ... std::mutex mu; Locker<std::mutex> locker (mu); // locks mu ... } // mu is automatically unlocked here (normal) or whenever an exception is thrown (abnormal) the client thread does not make explicit calls to lock/unlock a mutex, but relies on RAII to manage locking and (the important part) unlocking. No Semaphores??The lack of a class named Semaphore is mildly controversial. Some consider this omission a defect (it is not) and some consider it a disrespectful act toward this classic concept that goes all the way back to Dijkstra. In practice, the roles played by semaphores in, say, the pthread library, are all subsumed by other features of the C++11 support library, notably condition variables (which are objects of class condition_variable). The fact that a class named "semaphore" might have some behaviors that would be subject to arguments like "that is not part of the traditional semaphore" probably plays a role, as does the obvious fact that OS semaphores must be used in the implementation of the library, so that there would be two different technical definitions for the term.
Library Summary <thread>namespace std { class thread; void swap(thread& x, thread& y) noexcept; bool operator== (thread::id x, thread::id y) noexcept; bool operator!= (thread::id x, thread::id y) noexcept; bool operator< (thread::id x, thread::id y) noexcept; bool operator<= (thread::id x, thread::id y) noexcept; bool operator> (thread::id x, thread::id y) noexcept; bool operator>= (thread::id x, thread::id y) noexcept; template<class CharT, class Traits> basic_ostream<CharT, Traits>& operator<< (basic_ostream<CharT, Traits>& out, thread::id id); template <class T> struct hash; template <> struct hash<thread::id>; namespace this_thread { thread::id get_id() noexcept; void yield() noexcept; template <class Clock, class Duration> void sleep_until(const chrono::time_point<Clock, Duration>& abs_time); template <class Rep, class Period> void sleep_for(const chrono::duration<Rep, Period>& rel_time); } } Class std::threadclass thread { public: // types: class id; typedef /*implementation-defined*/ native_handle_type; // construct/copy/destroy: thread() noexcept; template <class F, class ...Args> explicit thread(F&& f, Args&&... args); ~thread(); thread(const thread&) = delete; thread(thread&&) noexcept; thread& operator=(const thread&) = delete; thread& operator=(thread&&) noexcept; // members: void swap(thread&) noexcept; bool joinable() const noexcept; void join(); void detach(); id get_id() const noexcept; native_handle_type native_handle(); // static members: static unsigned hardware_concurrency() noexcept; }; Class std::thread::idclass thread::id { public: id() noexcept; };
Library Summary <mutex>namespace std { class mutex; class recursive_mutex; class timed_mutex; class recursive_timed_mutex; struct defer_lock_t { }; constexpr defer_lock_t defer_lock { }; struct try_to_lock_t { }; constexpr try_to_lock_t try_to_lock { }; struct adopt_lock_t { }; constexpr adopt_lock_t adopt_lock { }; template <class Mutex> class lock_guard; template <class Mutex> class unique_lock; template <class Mutex> void swap(unique_lock<Mutex>& x, unique_lock<Mutex>& y) noexcept; template <class L1, class L2, class... L3> int try_lock(L1&, L2&, L3&...); template <class L1, class L2, class... L3> void lock(L1&, L2&, L3&...); struct once_flag; template<class Callable, class ...Args> void call_once(once_flag& flag, Callable func, Args&&... args); } Class std::mutexclass mutex { public: constexpr mutex () noexcept; ~mutex (); mutex(const mutex&) = delete; mutex& operator=(const mutex&) = delete; void lock (); bool try_lock (); void unlock (); typedef /*implementation-defined*/ native_handle_type; native_handle_type native_handle(); }; Class std::recursive_mutexclass recursive_mutex { public: recursive_mutex(); ~recursive_mutex(); recursive_mutex(const recursive_mutex&) = delete; recursive_mutex& operator=(const recursive_mutex&) = delete; void lock(); bool try_lock() noexcept; void unlock(); typedef /*implementation-defined*/ native_handle_type; native_handle_type native_handle(); }; Class std::timed_mutexclass timed_mutex { public: timed_mutex (); ~timed_mutex (); timed_mutex (const timed_mutex&) = delete; timed_mutex& operator=(const timed_mutex&) = delete; void lock (); bool try_lock (); template <class Rep, class Period> bool try_lock_for (const chrono::duration<Rep, Period>& rel_time); template <class Clock, class Duration> bool try_lock_until (const chrono::time_point<Clock, Duration>& abs_time); void unlock(); typedef /*implementation-defined*/ native_handle_type; native_handle_type native_handle (); }; Class std::recursive_timed_mutexclass recursive_timed_mutex { public: recursive_timed_mutex (); ~recursive_timed_mutex (); recursive_timed_mutex(const recursive_timed_mutex&) = delete; recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete; void lock (); void unlock (); bool try_lock () noexcept; template <class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time); template <class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time); typedef /*implementation-defined*/ native_handle_type; native_handle_type native_handle(); }; Class std::lock_guardtemplate <class Mutex> class lock_guard { public: typedef Mutex mutex_type; explicit lock_guard (mutex_type& m); lock_guard (mutex_type& m, adopt_lock_t); ~lock_guard (); lock_guard (lock_guard const&) = delete; lock_guard& operator=(lock_guard const&) = delete; private: mutex_type& pm; // exposition only }; Class std::unique_locktemplate <class Mutex> class unique_lock { public: typedef Mutex mutex_type; // construct/copy/destroy: unique_lock () noexcept; explicit unique_lock (mutex_type& m); unique_lock (mutex_type& m, defer_lock_t) noexcept; unique_lock (mutex_type& m, try_to_lock_t); unique_lock (mutex_type& m, adopt_lock_t); template <class Clock, class Duration> unique_lock (mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time); template <class Rep, class Period> unique_lock (mutex_type& m, const chrono::duration<Rep, Period>& rel_time); ~unique_lock (); unique_lock(unique_lock const&) = delete; unique_lock& operator=(unique_lock const&) = delete; unique_lock(unique_lock&& u) noexcept; unique_lock& operator=(unique_lock&& u) noexcept; // locking: void lock (); bool try_lock (); template <class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time); template <class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time); void unlock(); // modifiers: void swap(unique_lock& u) noexcept; mutex_type * release() noexcept; // observers: bool owns_lock () const noexcept; explicit operator bool () const noexcept; mutex_type* mutex () const noexcept; private: mutex_type * pm; // exposition only bool owns; // exposition only }; Class std::once_flagstruct once_flag { constexpr once_flag() noexcept; once_flag(const once_flag&) = delete; once_flag& operator=(const once_flag&) = delete; };
Library Summary <condition_variable>namespace std { class condition_variable; class condition_variable_any; void notify_all_at_thread_exit(condition_variable& cond, unique_lock<mutex> lk); enum class cv_status { no_timeout, timeout }; } Class std::condition_variableclass condition_variable { public: condition_variable(); ~condition_variable(); condition_variable(const condition_variable&) = delete; condition_variable& operator=(const condition_variable&) = delete; void notify_one() noexcept; void notify_all() noexcept; void wait(unique_lock<mutex>& lock); template <class Predicate> void wait(unique_lock<mutex>& lock, Predicate pred); template <class Clock, class Duration> cv_status wait_until(unique_lock<mutex>& lock, const chrono::time_point<Clock, Duration>& abs_time); template <class Clock, class Duration, class Predicate> bool wait_until(unique_lock<mutex>& lock, const chrono::time_point<Clock, Duration>& abs_time, Predicate pred); template <class Rep, class Period> cv_status wait_for(unique_lock<mutex>& lock, const chrono::duration<Rep, Period>& rel_time); template <class Rep, class Period, class Predicate> bool wait_for(unique_lock<mutex>& lock, const chrono::duration<Rep, Period>& rel_time, Predicate pred); typedef implementation-defined native_handle_type; native_handle_type native_handle(); }; Class std::condition_variable_anyclass condition_variable_any { public: condition_variable_any(); ~condition_variable_any(); condition_variable_any(const condition_variable_any&) = delete; condition_variable_any& operator=(const condition_variable_any&) = delete; void notify_one() noexcept; void notify_all() noexcept; template <class Lock> void wait(Lock& lock); template <class Lock, class Predicate> void wait(Lock& lock, Predicate pred); template <class Lock, class Clock, class Duration> cv_status wait_until(Lock& lock, const chrono::time_point<Clock, Duration>& abs_time); template <class Lock, class Clock, class Duration, class Predicate> bool wait_until(Lock& lock, const chrono::time_point<Clock, Duration>& abs_time, Predicate pred); template <class Lock, class Rep, class Period> cv_status wait_for(Lock& lock, const chrono::duration<Rep, Period>& rel_time); template <class Lock, class Rep, class Period, class Predicate> bool wait_for(Lock& lock, const chrono::duration<Rep, Period>& rel_time, Predicate pred); };
Library Summary <future>namespace std { enum class future_errc { broken_promise = /*implementation-defined*/, future_already_retrieved = /*implementation-defined*/, promise_already_satisfied = /*implementation-defined*/, no_state = /*implementation-defined*/ }; enum class launch : /*unspecified*/ { async = /*unspecified*/, deferred = /*unspecified*/, /*implementation-defined*/ }; enum class future_status { ready, timeout, deferred }; template <> struct is_error_code_enum<future_errc> : public true_type { }; error_code make_error_code(future_errc e) noexcept; error_condition make_error_condition(future_errc e) noexcept; const error_category& future_category() noexcept; class future_error; template <class R> class promise; template <class R> class promise<R&>; template <> class promise<void>; template <class R> void swap(promise<R>& x, promise<R>& y) noexcept; template <class R, class Alloc> struct uses_allocator<promise<R>, Alloc> : true_type {}; template <class R> class future; template <class R> class future<R&>; template <> class future<void>; template <class R> class shared_future; template <class R> class shared_future<R&>; template <> class shared_future<void>; template <class> class packaged_task; // undefined template <class R, class... ArgTypes> class packaged_task<R(ArgTypes...)>; template <class R> void swap(packaged_task<R(ArgTypes...)>&, packaged_task<R(ArgTypes...)>&) noexcept; template <class R, class Alloc> struct uses_allocator<packaged_task<R>, Alloc> : true_type {}; template <class F, class... Args> future<result_of_t<decay_t<F>(decay_t<Args>...)>> async(F&& f, Args&&... args); template <class F, class... Args> future<result_of_t<decay_t<F>(decay_t<Args>...)>> async(launch policy, F&& f, Args&&... args); } Class std::future_errorclass future_error : public logic_error { public: future_error(error_code ec); // exposition only const error_code& code() const noexcept; const char* what() const noexcept; }; Class std::promisetemplate <class R> class promise { public: promise(); template <class Allocator> promise(allocator_arg_t, const Allocator& a); promise(promise&& rhs) noexcept; promise(const promise& rhs) = delete; ~promise(); // assignment promise& operator=(promise&& rhs) noexcept; promise& operator=(const promise& rhs) = delete; void swap(promise& other) noexcept; // retrieving the result future<R> get_future(); // setting the result void set_value(/*see description*/); void set_exception(exception_ptr p); // setting the result with deferred notification void set_value_at_thread_exit(/*see description*/); void set_exception_at_thread_exit(exception_ptr p); }; Class std::futuretemplate <class R> class future { public: future() noexcept; future(future &&) noexcept; future(const future& rhs) = delete; ~future(); future& operator=(const future& rhs) = delete; future& operator=(future&&) noexcept; shared_future<R> share(); // retrieving the value /*see description*/ get(); // functions to check state bool valid() const noexcept; void wait() const; template <class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const; template <class Clock, class Duration> future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const; }; Class std::shared_futuretemplate <class R> class shared_future { public: shared_future() noexcept; shared_future(const shared_future& rhs); shared_future(future<R>&&) noexcept; shared_future(shared_future&& rhs) noexcept; ~shared_future(); shared_future& operator=(const shared_future& rhs); shared_future& operator=(shared_future&& rhs) noexcept; // retrieving the value /*see description*/ get() const; // functions to check state bool valid() const noexcept; void wait() const; template <class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const; template <class Clock, class Duration> future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const; }; Class std::packaged_tasktemplate<class> class packaged_task; // undefined template<class R, class... ArgTypes> class packaged_task<R(ArgTypes...)> { public: // construction and destruction packaged_task() noexcept; template <class F> explicit packaged_task(F&& f); template <class F, class Allocator> explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f); ~packaged_task(); // no copy packaged_task(const packaged_task&) = delete; packaged_task& operator=(const packaged_task&) = delete; // move support packaged_task(packaged_task&& rhs) noexcept; packaged_task& operator=(packaged_task&& rhs) noexcept; void swap(packaged_task& other) noexcept; bool valid() const noexcept; // result retrieval future<R> get_future(); // execution void operator()(ArgTypes... ); void make_ready_at_thread_exit(ArgTypes...); void reset(); }; |