前言
代码语言:javascript复制之前的工作项目基本不使用多线程,一直对多线程的理解比较浅显,一般应用也是主从两个线程,也不涉及资源锁,以及其他的各种锁,信号量之类的,更别提线程池之类的,这次也特意学习记录一下多线程。
库知识
C 11现在也有了自己的多线程库,从C 11的线程库开始学习了解。 库主要分为:
代码语言:javascript复制#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <future>
std::thread
std::thread类,主要用来创建创建线程,对线程对象进行相关操作,控制线程的生命周期。 std::thread 类成员函数主要如下:
代码语言:javascript复制//构造函数
thread() noexcept = default;
thread(thread&) = delete;
thread(const thread&) = delete;
thread(const thread&&) = delete;
thread(thread&& __t) noexcept { swap(__t); }
//析构函数
~thread()
{
if (joinable())
std::terminate();
}
//交换函数,用来交换底层句柄
void swap(thread& __t) noexcept { std::swap(_M_id, __t._M_id); }
//join状态函数,判断线程是否能被线程控制
bool joinable() const noexcept { return !(_M_id == id()); } 、
//join 等待线程执行结束
void join();
//线程分离函数
void detach();
//得到线程ID
thread::id get_id() const noexcept { return _M_id; }
//native_handle_type 得到与操作系统相关的原生线程句柄
typedef __gthread_t native_handle_type;
native_handle_type native_handle() { return _M_id.M_thread; }
//hardware_concurrency 获得当前程序最大支持的线程数,多线程一般代表系统核数
static unsigned int hardware_concurrency() noexcept;
std::mutex
互斥锁,主要用来线程同步,保证在同一时间内只有一个线程对某一资源进行读写操作。 std::mutex 类主要有以下几种类,mutex,recursive_mutex,timed_mutex,recursive_timed_mutex几种类。
mutex
基础类:
代码语言:javascript复制//加锁
void lock();
//解锁
void unlock();
//尝试锁
bool try_lock();
recursive_mutex
递归锁:允许在同一个线程内,多一个互斥量进行多次请求。即在同一个线程内,多次获取锁定同一个递归锁,且不会产生死锁。
代码语言:javascript复制//构造函数
recursive_mutex() = default;
recursive_mutex(const recursive_mutex&) = delete;
//析构函数
~recursive_mutex() = default;
//加锁
void lock();
//解锁
void unlock();
//尝试锁
bool try_lock();
timed_mutex
定时锁:
代码语言:javascript复制//构造函数
timed_mutex() = default;
timed_mutex(const timed_mutex&) = delete;
//析构函数
~timed_mutex() = default;
//加锁
void lock();
//解锁
void unlock();
//尝试锁
bool try_lock();
//等待锁,在调用时,在一个时间段内,如果锁被释放,加锁,否则,返回false。
template <class _Rep, class _Period>
bool try_lock_for(const chrono::duration<_Rep, _Period>& __rtime) { return _M_try_lock_for(__rtime); }
//等待锁,在某个时刻到达之前,如果锁被释放,加锁,否则,返回false
template <class _Clock, class _Duration>
bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime) { return _M_try_lock_until(__atime); }
recursive_timed_mutex
递归定时锁:具备递归锁和定时锁的所有特性。
代码语言:javascript复制//构造函数
recursive_timed_mutex() = default;
recursive_timed_mutex(const recursive_timed_mutex&) = delete;
//析构函数
~recursive_timed_mutex() = default;
//加锁
void lock();
//解锁
void unlock();
//尝试锁
bool try_lock();
//等待锁,在调用时,在一个时间段内,如果锁被释放,加锁,否则,返回false。
template <class _Rep, class _Period>
bool try_lock_for(const chrono::duration<_Rep, _Period>& __rtime) { return _M_try_lock_for(__rtime); }
//等待锁,在某个时刻到达之前,如果锁被释放,加锁,否则,返回false
template <class _Clock, class _Duration>
bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime) { return _M_try_lock_until(__atime); }
lock_guard
代码语言:javascript复制//构造函数
explicit lock_guard(mutex_type& __m) : _M_device(__m) { _M_device.lock(); }
lock_guard(mutex_type& __m, adopt_lock_t) noexcept : _M_device(__m) { } // calling thread owns mutex
lock_guard(const lock_guard&) = delete;
//析构函数
~lock_guard() { _M_device.unlock(); }
个人理解:类似于智能指针,在生命周期结束时,析构,能够自动解锁,不需要手动解锁,提供了一定的安全性。
unique_lock
代码语言:javascript复制//构造函数
unique_lock() noexcept : _M_device(0), _M_owns(false) { }
//构造函数,只允许显示调用
explicit unique_lock(mutex_type& __m) : _M_device(std::__addressof(__m)), _M_owns(false) { lock(); _M_owns = true; }
//构造函数,无互斥所有权
unique_lock(mutex_type& __m, defer_lock_t) noexcept: _M_device(std::__addressof(__m)), _owns(false){ }
//构造函数,尝试获得锁的所有权,而不阻塞
unique_lock(mutex_type& __m, try_to_lock_t) : _M_device(std::__addressof(__m)), M_owns(_M_device->try_lock()) { }
//构造函数,拥有互斥锁所有权
unique_lock(mutex_type& __m, adopt_lock_t) noexcept : _M_device(std::__addressof(__m)), M_owns(true){
// XXX calling thread owns mutex
}
//析构函数
~unique_lock() { if (_M_owns) unlock();}
//锁
void lock();
//尝试锁
bool try_lock();
//等待
bool try_lock_until();
bool try_lock_for();
//解锁
void unlock();
//交换所有权
void swap(unique_lock& __u);
//释放
mutex_type* release();
//返回锁状态
bool owns_lock();
//获取锁
mutex_type* mutex() const noexcept { return _M_device; }
个人理解:对锁对象的控制权,百度来的,通用互斥包装器,允许“延迟锁定,锁定的有限尝试、递归锁定、所有权转移和条件变量一同使用”,unique_lock 比 lock_guard 使用更加灵活,功能更加强大。但是使用unique_lock 需要付出更多的时间成本、性能成本。
std::condition_variable
std::condition_variable 条件变量,性能消耗小于std::mutex,对于线程同步,效率高于 std::mutex。std::conditon_variable 有两个接口 wait(),可以是线程处与休眠状态,另一个就是notify_one(),唤醒处于wait中的其中一个条件变量,(可能当时有很多条件变量处于wait状态)。notify_all(),唤醒所有处于wait状态的条件。
代码语言:javascript复制//构造函数
condition_variable() noexcept;
condition_variable(const condition_variable&) = delete;
//析构函数
~condition_variable() noexcept;
//唤醒单个线程
void notify_one() noexcept;
//唤醒所有线程
void notify_all() noexcept;
//休眠函数
void wait(unique_lock<mutex>& __lock) noexcept;
void wait(unique_lock<mutex>& __lock, _Predicate __p) { while (!__p()) wait(__lock);}
//休眠函数,等待时间点,线程收到通知或者指定时间点abs_time超时之前,线程都会处于阻塞状态,超时或者被唤醒,返回
cv_status wait_until(unique_lock<mutex>& __lock, const chrono::time_point<__clock_t, _Duration>& __atime) { return __wait_until_impl(__lock, __atime); }
cv_status wait_until(unique_lock<mutex>& __lock,const chrono::time_point<_Clock, _Duration>& __atime)
{
// DR 887 - Sync unknown clock to known clock.
const typename _Clock::time_point __c_entry = _Clock::now();
const __clock_t::time_point __s_entry = __clock_t::now();
const auto __delta = __atime - __c_entry;
const auto __s_atime = __s_entry __delta;
return __wait_until_impl(__lock, __s_atime);
}
bool wait_until(unique_lock<mutex>& __lock,const chrono::time_point<_Clock, _Duration>& __atime,
_Predicate __p) { while (!__p()) if (wait_until(__lock, __atime) == cv_status::timeout) return __p();
return true;}
//线程休眠,加了时间限制,在超时之前,处于休眠状态,如果超时或者被唤醒,wait_for 返回
cv_status wait_for(unique_lock<mutex>& __lock, const chrono::duration<_Rep, _Period>& __rtime)
{ return wait_until(__lock, __clock_t::now() __rtime); }
bool wait_for(unique_lock<mutex>& __lock, const chrono::duration<_Rep, _Period>& __rtime,
_Predicate __p){ return wait_until(__lock, __clock_t::now() __rtime, std::move(__p)); }