当前位置: 首页 > news >正文

C++多线程知识点梳理

0.前言

本篇主要围绕C++线程知识展开,本篇并不会一次性写完,而是会不断填充,以便后续回看。

1.线程基础概念

  • 线程与进程的区别:线程是进程的执行单元,共享进程资源(内存、文件描述符等),切换开销更小。
  • 并发与并行的区别:并发是任务交替执行,并行是任务同时执行(依赖多核CPU)。
  • C++标准库支持:<thread>头文件提供线程管理接口。

2.C++线程常用接口

2.1创建线程

通过std::thread构造函数,传入可调用对象(函数、Lambda、成员函数等)。从下面可以看到常用的线程创建方式。

#include<iostream>
#include<thread>
using namespace std;void test(){cout<<"test"<<endl;
}void test1(int i){cout<<"test(int i)"<<i<<endl;
}void test2(int& i){i++;cout<<"test(int i)"<<i<<endl;
}class ThreadDemo{public:void test(int i){cout<<"ThreadDemo::test(int i)"<<i<<endl;}
};int main(void){int i = 1;// 情形1:全局函数创建线程thread th1(test);thread th2(test1,i);// 情形2:成员函数创建线程ThreadDemo td;thread th3(&ThreadDemo::test,&td,i);// 引用传参thread th4(test2,std::ref(i));this_thread::sleep_for(1000ms);// 情形3:lambda创建线程thread th5([&i](){i = i+1;cout<<"lambda"<<i<<endl;});th1.join();th2.join();th3.join();th4.join();th5.join();cout<<i<<endl;return 0;
}

这里需要着重注意的是,如果采用引用(或指针)传参,一定要保证线程在执行完毕前,其参数不能被销毁。

 2.2线程回收

常见的线程回收包括:join()等待线程结束,detach()分离线程(后台运行)。

2.3线程参数

前文提到过,如果采用拷贝给线程传递参数安全性较高,但是如果传递引用参数的话,必须注意参数在线程使用过程中不能被释放。

比如有如下代码:

struct threaddemo
{int& i;threaddemo(int& i_) : i(i_) {cout<<&i<<endl;}void test(int i){cout<<i<<endl;}void operator() (){for (unsigned j=0 ; j<10 ; ++j){test(i);           // 访问局部变量引用}}
};int main(void){{int l_val=0;threaddemo tm(l_val);thread my_thread(tm);my_thread.detach();          // 2. 不等待线程结束}return 0;
}

如果实例化上述类中的对象并创建线程(main中),则会间接引用到局部变量,会出现安全问题。

2.4线程管理

每次创建一个线程都需要找个时机调用join(),这确实有点反人类了。常见的方法是采用RAII的方法。完整的代码大概是这样的:

#include<thread>
#include<iostream>
using namespace std;struct threaddemo
{int& i;threaddemo(int& i_) : i(i_) {cout<<&i<<endl;}void test(int i){cout<<i<<endl;}void operator() (){for (unsigned j=0 ; j<10 ; ++j){test(i);           // 1. 潜在访问隐患:悬空引用}}
};class RAIIthread{
public:thread& th;RAIIthread(thread& th_) : th(th_) {}~RAIIthread(){if(th.joinable())cout<<"exec"<<endl;th.join();}
};int main(void){{int l_val=0;cout<<&l_val<<endl;threaddemo tm(l_val);thread my_thread(tm);RAIIthread raii_thread(my_thread);// my_thread.join();          // 2. 不等待线程结束}return 0;
}

这样每次线程执行完毕后,就可以自动调用join了。

3.共享数据保护

当多个线程同时操作共享数据(或代码段)时,可能存在竞争问题。为此,C++采用了锁机制解决这个问题。

3.1mutex基本使用

这里直接给个简单的例子:在多线程场景下向一个数组中添加元素。代码实现如下:

#include<iostream>
#include<vector>
#include<thread>
#include<mutex>using namespace std;// 向容器添加数据的任务
mutex m;
void addTask(vector<int>& vec,int i){m.lock();for(int j = 0;j<i;j++){vec.push_back(j);}m.unlock();
}// 打印容器元素
void printVec(const vector<int>& vec){for(auto i:vec){cout<<i<<" ";}cout<<endl;
}
// 
int main(void){vector<int> data;vector<thread> ths;for(int i = 0;i<5;i++){ths.push_back(thread(addTask,std::ref(data),1000));}// 回收线程for(auto &th:ths){th.join();}printVec(data);return 0;
}

在主函数中创建了五个线程,每个线程都会向vector添加1000个元素。因为加了mutex的原因,程序输出的vector大小是5000个。但是不加锁则一定几率出现小于5000个元素,甚至会报错。

3.2RAII+mutex

从3.1程序中可以看到,锁的释放需要特定的时机。C++标准库提供了std::lock_guard模板类,它能在创建时自动加锁,并在析构时自动解锁,即RAII。

那任务函数可以简化为:

void addTask(vector<int>& vec,int i){lock_guard<mutex> lg(m);for(int j = 0;j<i;j++){vec.push_back(j);}
}

补充

后续内容慢慢补充!

http://www.dtcms.com/a/278111.html

相关文章:

  • 深入理解 Java Map 与 Set
  • 每天学一个八股(二)——详解HashMap
  • 封装---优化try..catch错误处理方式
  • 【echarts踩坑记录】为什么第二个Y轴最大值不整洁
  • Acrobat 表单中的下拉菜单(附示例下载)
  • 使用docker的常用命令
  • RS4585自动收发电路原理图讲解
  • 从 Manifest V2 升级到 Manifest V3 的注意事项
  • Extended Nested Arrays for Consecutive Virtual Aperture Enhancement
  • 财务管理体系——解读大型企业集团财务管理体系解决方案【附全文阅读】
  • Python异步编程
  • 57.第二阶段x64游戏实战-实时监控抓取lua内容
  • 利用低汇率国家苹果订阅,120 元开通 ChatGPT Plus
  • 14.使用GoogleNet/Inception网络进行Fashion-Mnist分类
  • docker基础部署
  • ID生成策略
  • 在新版本的微信开发者工具中使用npm包
  • 用信号量实现进程互斥,进程同步,进程前驱关系(操作系统os)
  • DOS下EXE文件的分析 <1>
  • MacBook Air通过VMware Fusion Pro安装Win11
  • 从代码学习深度强化学习 - DDPG PyTorch版
  • [Python 基础课程]列表
  • 【DataLoader的使用】
  • 力扣 hot100 Day43
  • Actor-Critic重要性采样原理
  • java valueOf方法
  • 【算法】贪心算法入门
  • SwiftUI 7 新 WebView:金蛇出洞,网页江湖换新天
  • 一些git命令
  • 若依框架集成阿里云OSS实现文件上传优化