c++多线程编程
  yB4UiPsMeGIh 2024年03月02日 37 0
C++

c++线程库:<thread>

创建线程:需要可调用的函数或者函数对象作为线程入口点

    例:std::thread threadname ( function_name , args...)   
  • 在C++中,当使用std::thread创建线程并传递类的成员函数时,需要使用&来获取成员函数的地址,同时还需要传递对象的指针(或引用)作为第一个参数。

例:(A为一个类,a为A的一个实例化对象) thread t (&A::func_name,&a,args)

补充:在使用多线程编程时,内存管理变得更加复杂,使用智能指针可以帮助我们更好避免内存泄漏

auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存

头文件:#include < memory >

  1. 【c++11已过时】auto_ptr<类型> 变量名(new 类型)

     ·ptr.get()    //获取智能指针托管的指针地址
     ·ptr.release()  //取消智能指针对动态内存的托管
     ·ptr.reset(arg)  //将参数的指针(不指定则为NULL),与托管的指针比较,如果地址不一致,那么就会析构掉原来托管的指针,然后使用参数的指针替代之
  • auto_ptr 主要有三大问题:
     · 复制和赋值会改变资源的所有权,不符合人的直觉。
     · 在 STL 容器中使用auto_ptr存在风险,因为容器内的元素必需支持可复制和可赋值。
     · 不支持对象数组的操作

因此c++11后使用unique_ptr shared_ptr和weak_ptr代替

  1. unique_ptr

函数:

    thread.join();      //执行join后,等待线程完成函数,主线程需要等待子线程运行结束了才可以结束 
    
    thread.detach();    //执行detach可以分离子线程,主线程不会等待子线程运行结束才结束
    
    thread.joinable();  //会返回一个bool值,判断是否已经使用过join或者detach *已调用时返回false*
    
    std::ref(para);         //thread的方法传递引用的时候,必须外层用ref来进行引用传递,否则会编译出错

调用类内私有成员函数:友元函数

例: void func_name();需要调用类内的私有成员函数
则在类内需要通过声明 friend void func_name();

c++11 互斥量

  • 头文件:#include <mutex>
  1. 基本互斥类: mutex _name;
    • 构造函数:mutex不允许拷贝构造,也不允许move拷贝,最初产生的mutex对象处于unlocked状态
    • lock():调用线程将锁住该互斥量,线程调用该函数会发生以下3种情况:
      (1) 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一直拥有该锁。
      (2) 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
      (3) 如果当前互斥量被当前调用线程锁住,则会产生死锁,,也就是说同一个线程中不允许锁两次。
    • try_lock():尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞,线程调用该函数会出现下面3种情况:
      (1) 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用unlock释放互斥量。
      (2) 如果当前互斥量被其他线程锁住,则当前调用线程返回false,而并不会被阻塞掉。
      (3) 如果当前互斥量被当前调用线程锁住,则会产生死锁。
  2. 递归互斥类【能进行多次锁定而不造成死锁】: recursive_mutex
    • 与mutex类似,但是能够进行多次lock,能够规避一些死锁问题
  3. 定时互斥类【可以锁定一定的时间】:time_mutex
  4. 定时递归互斥类:recursive_timed_mutex;

互斥量死锁

  1. lock_guard: c++标准库互斥量封装类,用于保护共享数据,防止多个线程同时访问统一资源而导致的数据竞争
    • 构造函数调用时,该互斥量自动锁定
    • 析构函数调用时,该互斥量自动解锁

例:lock_guard< Template > lock_name(obj);

  • lock_guard类是 non-copyable的。
  1. unique_lock: 可以对互斥量进行更加灵活管理,包括延迟加锁、条件变量、超时等
    • 成员函数:
      (1) lock() 尝试对互斥量加锁,若加锁失败则阻塞直到成功加锁
      (2) try_lock() 尝试枷锁,成功返回true,失败返回false

condition_variable及其使用场景

  • 头文件#include <condition_variable>
  • 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变量的条件成立而挂起;另一个线程使条件成立(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥量结合在一起。【常见用途:生产者-消费者模型】
  • 从条件变量的作用可以知道,在使用条件变量时,分为两个方面:
    (1) 用于通知已阻塞线程,共享变量已改变
    (2) 用于阻塞某一线程,直至该线程被唤醒
  1. 创建:condition_variable cv_name;
  2. cv_obj.wait(unique_lock_obj,arg) 消费者在共享资源不足时,通过wait函数等待,第二个参数arg为true时不等待,false时阻塞等待。

当前线程调用wait()后将被阻塞,直到另外某个线程调用notify_唤醒当前线程;当线程被阻塞时,该函数会自动调用std::mutex的unlock()释放锁,使得其它被阻塞在锁竞争上的线程得以继续执行。一旦当前线程获得通知(notify,通常是另外某个线程调用notify_唤醒了当前线程),wait()函数也是自动调用std::mutex的lock()。wait分为无条件被阻塞和带条件的被阻塞两种。

  1. cv_obj.notify_one() 唤醒某个等待(wait)线程。如果当前没有等待线程,则该函数什么也不做,如果同时存在多个等待线程,则唤醒某个线程是不确定的
  2. cv_obj.notify_all() 唤醒所有等待进程
  3. notify_all_at_thread_exit 当调用该函数的线程退出时,所有在 cond 条件变量上等待的线程都会收到通知

condition_variable_any 介绍
与 condition_variable 类似,只不过 condition_variable_any 的 wait 函数可以接受任何 lockable 参数,而condition_variable 只能接受 unique_lock< mutex > 类型的参数,除此以外,和 :condition_variable 几乎完全一样。

异步并发

1. async、future

  • 头文件: #include <future>
  • c++11引入的函数模板,用于异步执行一个函数,并返回到future对象,表示异步操作的结果。使用async可以方便进行异步编程,避免了手动创建和管理线程的麻烦

例:std::future< template > future_name = std::async(std::launch::async, func_name);

2. packaged_task

  • 类模板,用于将一个可调用对象(如函数、函数对象或Lambda表达式)封装为一个异步操作,并返回一个future对象,表示异步操作结果。package_task可以方便将一个函数或可调用对象转换为一个异步操作,供其他线程使用

例: std::packaged_task<returntype()> task_name(func_name);
// 此时得到的是封装好的task对象,需要通过创建线程执行 【记得使用move(task)把可移动对象放入线程执行】
auto result = task_name.get_future();

3. promise

  • 用于在线程中产生一个值,并在另一个线程中获取该值。通常与future和async一起使用,实现异步编程。
  • 创建:std::promise<tmplate> name
  • 获取值: name.get_future();+result.get()

原子操作:atomic

  • c++11标准库中的一个模板类,用于实现多线程编程环境下的原子操作。提供一种线程安全的方式访问和修改共享变量,可以避免多线程环境中的数据竞争问题
  • 头文件: #include <atomic>
  • 创建: std::atomic<template> data_name
  • 相较于加锁实现的互斥,在线程安全中,原子操作的性能更好
  • 常见std::atomic操作:
    load(): 将std::atomic变量的值加载到当前线程的本地缓存中并返回
    store(value): 将value的值存储到std::atomic变量中,这个操作是原子性的
    exchange(value): 将value的值存储到std::atomic变量中,并返回原先的值
    compare_exchange_weak(expected, val)compare_exchange_strong(expected, val):比较 std::atomic 变量的值和 expected 的值是否相同,如果相同,则将 val 的值存储到 std::atomic 变量中,并返回 true;否则,将 std::atomic 变量的值存储到 expected 中,并返回 false
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2024年03月02日 0

暂无评论

推荐阅读
  8ZfKOql7qM9w   15天前   9   0   0 C++
  mCuLBwVLn3Lu   13天前   16   0   0 C++
yB4UiPsMeGIh
作者其他文章 更多

2024-03-02