文章目录
- 一、std::thread
- 1.1 成员函数
- 1.2 案例
- 二、std::mutex
- 三、std::lock
- 3.1 lock_guard
- 3.2 unique_lock
- 3.3 lock_guard与unique_lock的对比
- 四、std::atomic
- 五、volatile
- 六、condition_variable
- 成员函数
- 七、future、promise、packaged_task
- 7.1 std::promise
- 7.2 std::future
- 7.3 std::packaged_task
- 八、async
- 九、call_once
一、std::thread
C++11中提供std::thread创建线程执行线程任务,不再需要使用繁琐的pthread_xxx;
1.1 成员函数
- 构造函数
// 1 提供默认构造;
thread() noexcept;
// 2 提供初始化构造函数,即创建线程任务
// Fn 执行函数
// Args 执行函数的参数列表
template <class Fn, class... Args>explicit thread (Fn&& fn, Args&&... args);
// 禁止拷贝构造
thread (const thread&) = delete;
// 3 提供移动构造函数
thread (thread&& x) noexcept;
- get_id
// 获取当前线程的ID
id get_id() const noexcept;
// 获取当前线程ID
std::this_thread::get_id()
- joinable
// 检查当前线程是否可接合
bool joinable() const noexcept;
// 【以下情况不可接合】:
// - 是默认构造
// - 从(构造另一个线程对象,或赋值给它)移动
// - 任何一个成员加入或脱离被调用
- join
// 主线程会等待该线程执行完成,当该线程执行完成后返回,当前线程对象jsonable为false
void join();
- detach
// 线程分离
// 将当前线程和主线程分离,允许独立执行;两个线程运行,不会阻塞,当线程执行玩,即被销毁
void detach();
- native_handle
// 获取操作系统原生的线程句柄pthread_xxx
// 若存在,则返回
native_handle_type native_handle();
- hardware_concurrency
// 获取当前CPU的个数
static unsigned hardware_concurrency() noexcept;
1.2 案例
#include <thread>
#include <iostream>
#include <unitsd.h>
void func1() {
std::cout << "func1" << std::endl;
std::cout << "func1 当前线程ID: " << std::this_thread::get_id() << std::endl;
std::cout << "func1 end" << std::endl;
}
void func2(int n) {
std::cout << "func2" << std::endl;
std::cout << "args: " << n << std::endl;
sleep(2);
std::cout << "fucn2 当前线程ID: " << std::this_thread::get_id() << std::endl;
std::cout << "func2 end" << std::endl;
}
int main() {
std::thread t1(func1);
std::thread t2(func2, 19);
t1.join();
t2.detach();
sleep(5);
return 0;
}
/// t1执行完会返回,t2为分离线程执行完毕后即销毁
二、std::mutex
用于保存多线程同时操作的共享数据;主要有四种类型:
- std::mutex:独占的互斥量;
- std::recursive_mutex:递归互斥量,可重入;
- std::timed_mutex:带超时的互斥量;
- std::recursive_timed_mutex:带超时的互斥量,可以递归使用;
========》递归锁的讨论《========
三、std::lock
可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁;
- std::lock_guard;
- std::unique_lock;
当进入到lock的当前作用域时自动上锁,离开当前作用域时自动释放锁;
3.1 lock_guard
// 该类只提供默认构造以及一个有参构造
// 拷贝构造和赋值操作符被delete
// 数据成员只有一个
3.2 unique_lock
/// 该类提供多种构造方式,可自设定是否需要完成持有该mutex,是否要设置超时;
/// 对于复制拷贝及赋值操作符都delete,但提供移动拷贝构造和赋值;
/// 额外提供lock、unlock、try_lock等;
3.3 lock_guard与unique_lock的对比
- unique_lock会更加灵活,但占用空间更大,速度会更慢一些,需要维护mutex的状态;
- unique_lock提供默认构造,不一定拥有mutex,unique_lock同样不可复制,但具有move语义,故可用于函数或STL的容器中;
- unique_lock额外提供lock,unlock,try_locl等
四、std::atomic
原子操作:多线程中最小的不可并行化的操作,故在多线程下,只有一个线程对其进行操作(不会出现例外);
- 通过互斥的访问来保证;
- 不需要通过加锁去锁的繁杂操作;
用法:
- atomic t ==> 可以通过T来指定任意类型;
- 原子类型只能从其模板参数类型中构造,不允许原子类型进行拷贝、移动构造;
- 可以隐式转换T类型;
【atomic_flag】:该类型为无锁的,可以用该类型来实现一个自旋锁;
五、volatile
参考文章【C++模块实现】| 【07】对于互斥、自旋锁、条件变量、信号量简介及封装 1.6
六、condition_variable
可参考之前使用pthread_xxx版本的:Linux【线程】 | 【01】线程、线程同步、线程安全
它可以阻塞一个线程或者个线程,直到有线程通知或者超时才会唤醒正在阻塞的线程,条件变量需要和锁配合使用;
成员函数
// 默认构造
condition_variable();
// 拷贝构造被禁止
condition_variable (const condition_variable&) = delete;
// 等待变量条件通知,传入unique_lock
void wait (unique_lock<mutex>& lck);
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
// 等待超时或直到通知,传入unique_lock
// 可通过wait_for的返回值是否为std::cv_status::timeout来判断当前通知是否超时
template <class Rep, class Period>
cv_status wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time);
// wait_until和wait_for类似,只不过时间段被替换成了时间点
template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time);
// 唤醒某个等待线程,如果没有线程在等待,那么这个函数什么也不做,如果有多个线程等待,唤醒哪个线程是随机的
void notify_one() noexcept;
// 唤醒所有的等待线程
void notify_all() noexcept;
// 枚举,判断状态
std::cv_status{
no_timeout,
timeout
}
七、future、promise、packaged_task
c++11关于异步操作提供了future相关的类,主要有std::future、std::promise和std::packaged_task;
- std::future:异步结果的传输通道,通过get()可获取线程函数的返回值,std::future是不可以复制的,若要复制放到容器中可以使用std::shared_future。
- std::promise:用来包装一个值,将数据和future绑定起来;
- std::packaged_task:用来包装一个调用对象,将函数和future绑定起来,方便异步调用;
7.1 std::promise
- 该类可存储T类型的值,提供给future对象;
- 该类通过get_future关联到futrue对象上,调用后,两个对象共享相同的共享状态;
- futrue是异步返回对象,它的get方法会阻塞等待promise设定val;
// 提供默认构造
promise();
// 提供带参数,可自定义分配器
template <class Alloc> promise (allocator_arg_t aa, const Alloc& alloc);
// 禁用拷贝构造
promise(const promise&) = delete;
// 提供移动拷贝构造
promise(promise&& x) noexcept;
// 获取与共享状态相关联的future对象
future<T> get_future();
// 将val存储为共享状态的值,共享状态变为ready
void set_value (const T& val);void set_value (T&& val);
// 将异常指针p存储在共享状态,该状态变为ready
// 解除future::get的阻塞并抛出由p指向的异常对象
void set_exception (exception_ptr p);
7.2 std::future
- 是一个可从某个提供者对象或函数检索值的对象;
- 再某个线程上调用futrue::get将会阻塞该线程,知道提供者提供好共享状态和值,以此来实现两个线程设置值的同步;
// 提供默认构造
future() noexcept;
// 禁止拷贝构造
future (const future&) = delete;
// 提供移动构造
future (future&& x) noexcept;
// 返回一个shared_future对象,用于获取未来对象的共享状态
shared_future<T> share();
// 当共享状态就绪时,返回存储在共享状态中的值;若未就绪,则等待
T get();
R& future<R&>::get();
void future<void>::get();
// 检查future对象当前是否与共享状态相关联
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;
7.3 std::packaged_task
- 包装了一个可调用元素,允许异步检索其结果;
- 类似std::function,但该类是将其结果自动传递给future对象;
// 提供默认构造
packaged_task() noexcept;
// 提供带参构造,为函数签名
template <class Fn> explicit packaged_task (Fn&& fn);
// 提供带参构造,为分配器及函数签名
template <class Fn, class Alloc> explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
// 禁止拷贝构造
packaged_task (packaged_task&) = delete;
// 提供移动构造
packaged_task (packaged_task&& x) noexcept;
// 检查有效的共享状态
bool valid() const noexcept;
// 返回与对象的共享状态相关联的future对象
future<Ret> get_future();
// 调用存储的任务,转发参数作为它的参数
void operator()(Args... args);
// 使用新的共享状态重置对象,同时保持相同的存储任务
void reset();
八、async
- 返回的对象是一个future对象,保存函数的执行结果;
- 使用该类,即直接传入一个函数名称, 后续跟上参数即可;
- 策略标志,枚举类型:
- std::launch::async:调用async即开始创建线程;
- std::launch::deferred:没有创建线程,在主线程中调用入口函数;为延时调用,当调用future的wait和get,才执行入口函数;
九、call_once
- 来保证某一函数在多线程环境中只调用一次,它需要配合std::once_flag使用;
std::once_flag onceflag;
void CallOnce() {
std::call_once(onceflag, []() {
cout << "call once" << endl;
});
}
int main() {
std::thread threads[5];
for (int i = 0; i < 5; ++i) {
threads[i] = std::thread(CallOnce);
}
for (auto& th : threads) {
th.join();
}
return 0;
}