【C++11新特性】| 并发编程

一、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::futurestd::promisestd::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;
}

版权声明:本文为weixin_45926547原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。