C++线程

news/2024/7/7 20:24:52

多线程

常见函数

构造函数

函数对应作用
thread() noexcept空线程,什么都不做,noexcept作用是标识该函数不会抛出任何异常(简化代码生成过程)
template <class Fn, class… Args> explicit thread(Fn&& fn, Args&&… args)创建一个线程,以args为参数执行fn函数
thread(thread&& x) noexcept构造一个与x相同的对象,会破坏x对象

成员函数

函数作用
void join()等待线程结束并清理资源(会阻塞)
bool joinable()返回线程是否可以执行join函数
void detach()将线程与调用其的线程分离,彼此独立执行(此函数必须在线程创建时立即调用,且调用此函数会使其不能被join)
std::thread::id get_id()获取线程id
thread& operator=(thread &&rhs)见移动构造函数(如果对象是joinable的,那么会调用std::terminate()结果程序)

多线程的基本使用

使用lamda表达式初始化线程

void doit()
{
  std::cout << "Hello " << std::endl;
}
void test01()
{
  std::thread t1([]
                 { std::cout << "Linux" << std::flush; }),
      t2(doit);
  t1.join();
  t2.join();
}

让thread执行带参函数

void consumer(int id , int  n)
{
  for(int i = 1; i <= n; ++i);
  std::cout << "thread" << id << "is work done!!!" << std::endl;
}
//test thread 
void test02()
{
    std::thread th[10];
    for(int i = 0; i < 10 ; ++i)
    {
       th[i] = std::thread(consumer, i, 10000000);
    }
    for(int i = 0  ; i < 10 ; ++i)
    {
       th[i].join();
    }
}

传递模板类型的函数给线程

std::ref 可以包装按引用传递的值。
std::cref 可以包装按const引用传递的值。

template<class T >
void CHange_Value(T& x,T value)
{
   x = value;
}
void test03()
{
   std::thread th[100];
   int nums[100];
   for(int i = 0 ; i < 100; ++i)
   {
      th[i] = std::thread( CHange_Value<int>, std::ref(nums[i]), i+1);
   }
   for(int i = 0; i < 100; ++i)
   {
       th[i].join();
       if(i !=0 && i % 10 == 0)
       {
          std::cout << std::endl;
       }
       std::cout << nums[i] << "  ";
   }
    std::cout << std::endl;
}

保护临界资源(全局类数据)的两种方式

线程互斥

函数声明:
#include<mutex>
成员函数:
std::mutex _mutex;
//加锁:_mutex.lock();
       _mutex.trylock(); //未被上锁,则该函数实现上锁+ 返回 true    已被上锁返回false
//解锁: _mutex.unlock();
#include<mutex>
std::mutex _mutex;
int n = 0;
void Count_Hun()
{
   for(int i = 0; i < 100; ++i)
   {
      _mutex.lock();
      n++;
      _mutex.unlock();
   }
}
void test04()
{
  std::thread th[100];
  for(std::thread& s : th)
  {
     s = std::thread(Count_Hun);
  }
  for(std::thread& s : th)
  {
     s.join();
  }
  std::cout << n << std::endl;
}

优点:执行逻辑一目了然,避免了数据不一致现象。

缺点:频繁的加锁/解锁造成效率的低下。

加锁所带来的问题:

  1. 在加锁与解锁途中,函数内部退出造成锁未释放的情况。
  2. 申请锁顺序不一致造成双方都占有部分资源,但都无法推进的情况。

如何解决?

  1. 通过RAII思想来解决,将锁的生命周期交由对象管理。本质来说就是构造加锁,出了作用域调用析构,在析构函数中解锁这个思想

std::lock_guard std::mutex 用来管理锁的生命周期 std::unique_lock --两者对比位于下方代码行

void Count_Hun()
{
  for (int i = 0; i < 100; ++i)
  {
    std::lock_guard<std::mutex> lg(_mutex);
    //_mutex.lock();
    n++;
    // _mutex.unlock();
  }
}
void test07()
{
  std::thread th[100];
  for (std::thread &s : th)
  {
    s = std::thread(Count_Hun);
  }
  for (std::thread &s : th)
  {
    s.join();
  }
  std::cout << n << std::endl;
}



compare:
void Count_Hun()
{
  for (int i = 0; i < 100; ++i)
  {
    std::unique_lock<std::mutex> ul(_mutex);// 相比较std::lock_guard来说其更加灵活,可以手动控制加锁粒度,因为lock_guard不提供解锁方法
    //而只是根据其生命周期决定,但unique_lock却可以在作用域内手动解锁,更加灵活
    n++;
    ul.unlock();
    
    
  }
}
  1. 使用std::lock对多个对象同时上锁(一个锁的话会报错)
void Count_Hun()
{
  for (int i = 0; i < 100; ++i)
  {
    //std::lock_guard<std::mutex> lg(_mutex);
    std::lock(_mutex,_mutex1);
    //_mutex.lock();
    n++;
    // _mutex.unlock();
    _mutex1.unlock();
    _mutex.unlock();
  }
}

原子变量

可以初始化的类型: 如bool 、char 、int 等。初始化方式

兼容C: 如std::atomic src = ATOMIC_VAR_INIT(0);

C++:std::atomic src(0);

#include<atomic>
std::atomic<int> tickets(0);
void Add_Tickets()
{
  for (int i = 0; i < 100; ++i)
  {
    tickets++;
  }
}
void test05()
{
  std::thread th[100];
  for (std::thread &s : th)
  {
    s = std::thread(Add_Tickets);
  }
  for (std::thread &s : th)
  {
    s.join();
  }
  std::cout << tickets << std::endl;
}

C++11中this_thread

上面讲了那么多关于创建、控制线程的方法,现在该讲讲关于线程控制自己的方法了。
<thread>头文件中,不仅有std::thread这个类,而且还有一个std::this_thread命名空间,它可以很方便地让线程对自己进行控制。

函数作用
std::thread::id get_id() noexcept获取当前线程id
template<class Rep, class Period>void sleep_for( const std::chrono::duration<Rep, Period>& sleep_duration )等待sleep_durationsleep_duration是一段时间)
void yield() noexcept暂时放弃线程的执行,将主动权交给其他线程(放心,主动权还会回来)
std::atomic<bool> ready(false);
void sleep(uintmax_t ms)
{
  std::this_thread::sleep_for(std::chrono::microseconds(ms));
}
void count()
{
  while(!ready) 
  std::this_thread::yield(); //**暂时**放弃线程的执行,将主动权交给其他线程(放心,主动权还会回来)
  for(int i = 0; i <= 2000000000; ++i);
  std::cout << "Thread" << std::this_thread::get_id() << "Finished" << std::endl;
}
void test06()
{
    std::thread th[10];
    for(int i = 0 ; i < 10 ;  ++ i)
    {
        th[i] = std::thread(count);
    }
    ::sleep(5000);
    ready = true;
    std::cout << "start" << std::endl;
    for(int i = 0 ; i < 10 ; ++i)
    {
      th[i].join();
    }
}

C++条件变量

#include<condition_variable>
#include<queue>
#include<random>
#include<ctime>
struct task{
  int x;
  int y;
  task(int _x,int _y):x(_x),y(_y)
  {}
  int operator()(){
      return x + y;
  }
};
//test for cindition_variable
std::condition_variable cv;
std::queue<task > tasks; //任务队列



void consumer01()
{
    while(true)
    {
      std::this_thread::sleep_for(std::chrono::microseconds(1000));
      std::unique_lock<std::mutex> ul(_mutex);
      while(tasks.empty())
      {
        //为了避免被虚假唤醒,有多消费者的情况下
        cv.wait(ul);
      }
      task t = tasks.front();
      std::cout << t.x<< "+" << t.y << " = " << t() << std::endl;
      tasks.pop();
    }
}
void productor()
{
    while(true)
    {
       std::this_thread::sleep_for(std::chrono::microseconds(500));
       std::unique_lock<std::mutex> ul(_mutex);
       int x = rand() % 10;
       int y = rand() % 10;
       task t(x,y);
       tasks.push(t);
       std::cout << "put a new task to the queue:" << " " << t.x << " + " << t.y << " = " << " ? " << std::endl; 
       cv.notify_all();
    }
}
void test()
{
   srand(time(NULL));
   std::thread t1(consumer01);
   std::thread t2(productor);
   t1.join();
   t2.join();
}

C++ future promise

C++中的futurepromise是一对在多线程编程中用来实现异步计算的工具。future对象用来表示异步操作的结果,而promise对象则用来生成这个结果。当程序需要实现异步操作时,可以使用promise对象生成一个future对象,将计算任务提交到一个线程中执行,同时程序可以继续执行其他任务。当异步操作完成后,计算结果将被写入promise对象中,并最终通过future对象被返回给程序使用。

通过使用futurepromise,我们可以将程序中的计算分成多个独立的线程,从而提高程序的执行效率和响应速度。同时,通过使用这些工具,我们可以有效地处理多个异步计算任务的执行顺序,并避免因为缺少资源导致死锁情况的发生。

注:其不支持赋值操作(std::promise t ; std::promise t1 = t (error); ),但可以支持move语义

(std::promise t ; std::promise t1 = std::move(t)😉。

背景:c++线程没有提供一个我们获取返回值的一个接口。比如:

#include<condition_variable>
std::condition_variable cv;
void task(int x,int y,int &ref)
{
   std::unique_lock<std::mutex> ul(_mutex);
   ref = x + y;
   cv.notify_one();

}
void test01()
{
  int ret  = 0 ;
  //背景:普通线程执行无法获取到线程执行结果,我们只可以通过传引用的方式去获取线程结果,通过加锁/条件变量来控制执行的顺序,非常复杂。  
  std::thread t1(task,1,2,std::ref(ret));
  std::unique_lock<std::mutex> ul(_mutex);
  cv.wait(ul);
  std::cout << ret << std::endl;
  t1.join();
}

第一种:获取到线程执行结果

void task(int x,int y, std::promise<int> &ref)
{ 
   ref.set_value ( x + y);
}
void test01()
{
  //用法1:获取到程序执行的结果,不使用任何的条件变量及锁
  int ret  = 0 ;  
  std::promise<int> t;
  std::future<int> fu = t.get_future(); //两个数据结构之间做了连接,此时调用fu.get()的时候,会阻塞在那里,知道子线程(即就是set_value执行)返回
  std::thread t1(task,1,2,std::ref(t));
  std::cout << fu.get() << std::endl;
  t1.join();
}

第二种:线程先开辟,随后等值到来再执行

#include<future>

void task(int x,std::future<int>& y, std::promise<int> &ref)
{ 
   ref.set_value ( y.get() + x);
}
void test01()
{
  //用法2:先开辟出线程,然后延迟去执行(线程的执行依赖后面设置的结果)
  int ret  = 0 ;  
  std::promise<int> t;
  std::future<int> fu = t.get_future(); //两个数据结构之间做了连接,此时调用fu.get()的时候,会阻塞在那里,知道子线程(即就是set_value执行)返回
  std::promise<int> t_in;
  std::future<int> fu_in = t_in.get_future();
  std::thread t1(task,1,std::ref(fu_in),std::ref(t));
  t_in.set_value(12);
  std::cout << fu.get() << std::endl;
  t1.join();
}

shared_future

在C++中,shared_future是一个类模板,用于表示一种可用于异步操作的共享状态,可以被多个线程读取。它是std::future的另一个版本,shared_future可以从future对象创建,也可以将其从其他shared_future对象复制。

shared_future对象的get()操作是线程安全的,多个线程可以同时调用这个函数,但是对于其他的操作,需要线程之间进行同步。

以下是一个简单的示例,展示了如何使用shared_future来并行地计算两个数字的乘积:

第一好处:线程安全,可同时调用这个对象 第二:不需要转换,直接传值即可

#include <iostream>
#include <future>

int main() {
    std::promise<int> prom;
    std::shared_future<int> fut(prom.get_future());

    std::cout << "Calculating in parallel (threads)...\n";
    auto prod = [](std::shared_future<int> f1, std::shared_future<int> f2) {
        std::cout << "Thread id: " << std::this_thread::get_id() << '\n';
        int val1 = f1.get();
        int val2 = f2.get();
        return val1 * val2;
    };
    auto f1 = std::async(std::launch::async, prod, fut, fut);
    auto f2 = std::async(std::launch::async, prod, fut, fut);

    prom.set_value(5);

    std::cout << "Results are: " << f1.get() << ", " << f2.get() << '\n';

    return 0;
}

这个示例会创建一个共享的future对象,两个线程并行地计算这个对象中的值,并返回一个新的future对象,别的线程用get()来等待这些计算的结果。

std::packaged_task std::async

std::async

std::async :可能并不是真正的去开启一个线程,而是根据需要去开启的。

返回值: std:;future fu = std::async(task,args…);

如果想必须采用新线程去处理应该怎么办?

std::future ret = std::async(std::launch::async,task,1,2);

std::cout << "return_value is " << ret.get() << std::endl;

//mean:延迟处理,意思是在真正需要的时候才去处理它,比如在cout那行,ret.get()的时候再去处理 – 同一个线程去处理

std::future ret = std::async(std::launch::deferred,task,1,2); -->当缺省第一个参数的时候,默认采取的也是这种策略。

std::cout << "return_value is " << ret.get() << std::endl;

std::async(std::launch::deferred| std::launch::async,task,1,2)== std::async(task,args…);

#include<future>
int task(int x,int y)
{ 
   return x + y;
}
void test01()
{
 
  // std::async 返回值: std::future 类型
  //std::future<int> ret = std::async(task,1,2);
  //std::future<int> ret = std::async(std::launch::async,task,1,2);
  //mean:延迟处理,意思是在真正需要的时候才去处理它,比如在cout那行,ret.get()的时候再去处理
  std::future<int> ret = std::async(std::launch::deferred,task,1,2);
  std::cout << "return_value is " << ret.get() << std::endl;

}

std::packaged_task 任务打包

std::packaged_task是一个可以将函数或可调用对象封装为异步任务的类模板

//type: std::packaged_task<return_type(args)> tk (function_name)

method1:

#include<future>
int task(int x,int y)
{ 
   return x + y;
}
void test01()
{
  //type: std::packaged_task<return_type(args)> tk (function_name)
   std::packaged_task<int(int,int)> tk (task) ;
   tk(1,2);
   //运行到tk.get_future().get()时,去调用tk 
   std::cout << "value is " << tk.get_future().get() << std::endl;

}

mothod2:

#include <iostream>
#include <future>
#include <thread>

int factorial(int n) {
    int res = 1;
    for (int i = 1; i <= n; ++i)
        res *= i;
    return res;
}

int main() {
    std::packaged_task<int(int)> task(factorial);
    std::future<int> fut = task.get_future();

    std::thread thr(std::move(task), 6);

    int result = fut.get();

    std::cout << "factorial(6) = " << result << '\n';

    thr.join();

    return 0;
}

在这个代码中,我们创建了一个std::packaged_task对象,并将一个计算阶乘的函数factorial作为其构造函数的参数。然后,我们使用std::future对象通过调用get_future()方法来获取异步任务的返回值。之后,我们通过创建新的线程来执行该异步任务,并通过调用future对象的get()方法来获取其返回值。最后,我们输出这个返回值并让新线程加入到主线程中。


http://lihuaxi.xjx100.cn/news/1169450.html

相关文章

VB一款实现图像浏览的ActiveX控件

利用GDI实现浏览图片的ActiveX控件&#xff0c;功能:支持读取PNG格式的图片&#xff0c;支持鼠标飞轮对图片进行缩放&#xff0c;镜像&#xff0c;移动等功能&#xff0c;其中用到了功能强大的GDI&#xff0c;GDI&#xff0c;对初及vb编程爱好者运用GDI-API有很大的研究价值&am…

Python实现温度植被干旱指数(TVDI)的计算

前言 温度植被干旱指数&#xff08;Temperature Vegetation Dryness Index&#xff0c;TVDI&#xff09;是一种基于光学与热红外遥感通道数据进行植被覆盖区域表层土壤水分反演的方法。作为同时与归一化植被指数(NDVI)和地表温度(LST)相关的温度植被干旱指数(TVDI)可用于干旱监…

【leetcode刷题】剑指offer基础版(完结)

leetcode刷题笔记—剑指offer 剑指 Offer 05. 替换空格 class Solution { public:string replaceSpace(string s) {int len s.size();string g;for(int i 0; i < len; i){if(s[i] ){g "%20";continue;}g s[i];}return g;} };剑指 Offer 58 - II. 左旋转字…

自动化测试框架类型,你知道几种?此处介绍5种比较常见的

每一个测试人员都应该了解每种框架的优缺点&#xff0c;以帮助你的团队更好地确定最适合的测试的框架&#xff0c;以达到事半功倍。 什么是测试自动化框架? 自动化测试框架就是用于测试自动化的框架。具体来说&#xff0c;它提供了自动化测试用例编写、自动化测试用例执行、自…

设计模式实战 - 工厂模式实现总览页面工作进展指标查询

设计模式实战 - 工厂模式实现总览页面工作进展指标查询 1. 请求入口 ProgressController PostMapping("/progress/indicators") ApiOperation(value "总览工作进展") PreAuthorize("hasAnyAuthority(superAdmin,overViewQuery,incidentQuery,alert…

01-JSON-概述

JSON&#xff08;JavaScript Object Notation&#xff09;是一种文本格式的数据交换标准&#xff0c;由Douglas Crockford发明。它可以表示简单的数据类型&#xff0c;如数字、字符串、布尔值和null&#xff0c;以及复杂的数据类型&#xff0c;如对象和数组等。JSON被广泛应用于…

行业报告 | 2022文化科技十大前沿应用趋势(下)

原创 | 文 BFT机器人 04 商业创新 趋势7&#xff1a;区块链技术连接传统文化&#xff0c;数字藏品市场在探索中发展 核心内容&#xff1a; 2022年&#xff0c;数字藏品在区块链技术的助力下应运而生。狭义的数字藏品是指使用区块链技术、基于特定的文化资源所生成唯一的数字凭…

Emacs之高效切换窗口(九十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…