thread
thread头文件主要包含thread类和this_thread命名空间。
namespace std {
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);
}
}
thread::id是一个轻量级的平凡拷贝的类,作为std::thread对象的唯一标识符,库为它实现了一些比较操作符、operator <<、和hash特化。
namespace std {
class thread {
public:
class id;
typedef /* 由实现定义 */ native_handle_type;
// 构造/复制/销毁:
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;
// 成员:
void swap(thread&) noexcept;
bool joinable() const noexcept; //检查 thread 对象是否标识活跃的执行线程
void join(); //阻塞当前线程,直至 *this 所标识的线程完成其执行
void detach();
id get_id() const noexcept;
native_handle_type native_handle();
// 静态成员:
static unsigned hardware_concurrency() noexcept;
};
}
如果一个thread对象标识着一个活跃的运行线程,称之为joinable,具体来说是判断std::thread::get_id()的返回值是不是等于std::thread::id()(std::thread::id的默认值)。因此thread经默认构造后不是joinable的;一个运行完毕但还没被joined的线程仍被认为是活跃的,所以是joinable的;一个被joined的线程是非joinable的。
#include <iostream>
#include <thread>
#include <chrono>
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::thread t;
std::cout << "before starting, joinable: " << std::boolalpha << t.joinable() << '\n';
t = std::thread(foo);
std::cout << "after starting, joinable: " << t.joinable() << '\n';
t.join();
std::cout << "after joining, joinable: " << t.joinable()<< '\n';
}
mutex
mutex头文件主要包括:
- mutex系列类
std::mutex,最基本的 mutex 类。
std::timed_mutex,定时 mutex 类。
std::recursive_mutex,递归 mutex 类。
std::recursive_timed_mutex,定时递归 mutex 类。 - lock 类
std::lock_guard,方便线程对互斥量上锁。
std::unique_lock,方便线程对互斥量上锁,但提供了更详细的上锁和解锁控制。 - 辅助类型
std::once_flag
std::adopt_lock_t
std::defer_lock_t
std::try_to_lock_t - 函数
std::try_lock,尝试同时对多个互斥量上锁。
std::lock,可以同时对多个互斥量上锁。
std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。
mutex的概要声明:
class mutex {
public:
constexpr mutex() noexcept;
~mutex();
mutex(const mutex&) = delete;
mutex& operator=(const mutex&) = delete;
void lock();
bool try_lock();
void unlock();
typedef /* 由实现定义 */ native_handle_type;
native_handle_type native_handle();
};
recursive_mutex 允许同一个线程对互斥量多次上锁,来获得对互斥量对象的多层所有权。
timed_mutex 比 mutex 多了两个成员函数:
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);
try_lock_for 接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住,如果在此期间获得了对互斥量的锁,返回true,如果超时仍未获得锁,则返回 false。
m.try_lock_until(std::chrono::system_clock::now() + std::chrono::seconds(5));
等价于m.try_lock_for(std::chrono::seconds(5));
lock_guard和unique_lock
lock_guard是一个mutex wrapper,概要声明:
template <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;
};
构造时加锁,析构时解锁,adopt构造接管一个已上锁的mutex。
unique_lock实现的功能类似,但提供更精细的控制,概要声明:
template <class Mutex>
class unique_lock {
public:
typedef Mutex mutex_type;
// 构造/复制/销毁:
unique_lock() noexcept; (1)
unique_lock( unique_lock&& other ) noexcept; (2)
explicit unique_lock(mutex_type& m); (3)
unique_lock(mutex_type& m, defer_lock_t) noexcept; (4)
unique_lock(mutex_type& m, try_to_lock_t); (5)
unique_lock(mutex_type& m, adopt_lock_t); (6)
template <class Clock, class Duration>
unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time); (7)
template <class Rep, class Period>
unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time); (8)
~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;
// 锁定:
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();
// 修改器:
void swap(unique_lock& u) noexcept;
mutex_type *release() noexcept; (9)
// 观察器:
bool owns_lock() const noexcept; //检查mutex是否已上锁
explicit operator bool () const noexcept; //同上
mutex_type* mutex() const noexcept;
private:
mutex_type *pm;
bool owns;
};
(1) 构造无关联互斥的 unique_lock。
(2) 移动构造函数。以 other 的内容初始化 unique_lock 。令 other 无关联互斥。
(3-8) 构造以 m 为关联互斥的 unique_lock 。另外:
3) 通过调用 m.lock() 锁定关联互斥。
4) 不锁定关联互斥。
5) 通过调用 m.try_lock() 尝试锁定关联互斥而不阻塞。
6) 假定调用方线程已占有 m 。
7) 通过调用 m.try_lock_for(timeout_duration) 尝试锁定关联互斥。
8) 通过调用 m.try_lock_until(timeout_time) 尝试锁定关联互斥。
(9) release断开*this
和对应mutex(如果存在的话)的联系。release不会进行解锁操作,因此如果*this
拥有mutex的所有权,调用者应该负责mutex的解锁。release返回对应mutex,如果没有对应mutex则返回空指针。
std::adopt_lock_t,std::defer_lock_t,std::try_to_lock_t是为了辅助lock_guard和unique_lock的结构体类型,分别有std::adopt_lock,std::defer_lock,std::try_to_lock常量对象存在。
函数
// 依次调用每个对象的 try_lock
// 若调用 try_lock 失败,则不再进一步调用 try_lock,并对任何已锁对象调用 unlock
// 若调用 try_lock 抛出异常,则在重抛前对任何已锁对象调用 unlock
// 成功时返回 -1 ,否则为锁定失败对象的下标值(从0开始计数)
template <class L1, class L2, class... L3>
int try_lock(L1&, L2&, L3&...);
// 锁定给定的可锁定对象,使用免死锁算法避免死锁
// 若调用 lock 或 unlock 导致异常,则在重抛前对任何已锁的对象调用 unlock
template <class L1, class L2, class... L3>
void lock(L1&, L2&, L3&...);
struct once_flag {
constexpr once_flag() noexcept;
once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;
};
template<class Callable, class ...Args>
void call_once(once_flag& flag, Callable func, Args&&... args);
call_once 执行一次可调用对象 f ,即使同时从多个线程调用。细节为:
- 若在调用 call_once 的时刻, flag 指示已经调用了 f ,则 call_once 立即返回。
- 否则, call_once 以参数
std::forward<Args>(args)...
调用 std::forward<Callable>(f)
。不同于 std::thread 构造函数或 std::async ,call_once 不移动或复制参数,因为不需要转移它们到另一执行线程。- 若该调用抛异常,则传播异常给 call_once 的调用方,并且不翻转 flag ,以令其他调用继续尝试。
- 若该调用正常返回,则翻转 flag ,并保证以同一 flag 对 call_once 的其他调用立即返回。
call_once 的例子:
#include <iostream>
#include <thread>
#include <mutex>
std::once_flag flag1, flag2;
void simple_do_once()
{
std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}
void may_throw_function(bool do_throw)
{
if (do_throw) {
std::cout << "throw: call_once will retry\n";
throw std::exception();
}
std::cout << "Didn't throw, call_once will not attempt again\n"; // 保证一次
}
void do_once(bool do_throw)
{
try {
std::call_once(flag2, may_throw_function, do_throw);
}
catch (...) {
}
}
int main()
{
std::thread st1(simple_do_once);
std::thread st2(simple_do_once);
std::thread st3(simple_do_once);
std::thread st4(simple_do_once);
st1.join();
st2.join();
st3.join();
st4.join();
std::thread t1(do_once, true);
std::thread t2(do_once, true);
std::thread t3(do_once, false);
std::thread t4(do_once, true);
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}
主要参考自cppreference