c++11线程支持库:thread和mutex

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值