C++多线程编程基础

news2024/11/20 11:21:43

1.创建线程Thread

首先要引入头文件#include,管理线程的函数和类在该头文件中声明,其中包括std::thread类。

语句"std::thread th1(proc1);"创建了一个名为th1的线程,并且线程th1开始执行。

实例化std::thread类对象时,至少需要传递函数名作为参数。如果函数为有参函数,如"void proc2(int a,int b)“,那么实例化std::thread类对象时,则需要传递更多参数,参数顺序依次为函数名、该函数的第一个参数、该函数的第二个参数,···,如"std::thread th2(proc2,a,b);”。
总体来说,std::thread的构造函数会拷贝传入的参数:

a.当传入参数为基本数据类型(int,char,string等)时,会拷贝一份给创建的线程;
b. 当传入参数为指针时,会浅拷贝一份给创建的线程,也就是说,只会拷贝对象的指针,不会拷贝指针指向的对象本身。
c. 当传入的参数为引用时,实参必须用ref()函数处理后传递给形参,否则编译不通过,此时不存在“拷贝”行为。

使用std::thread时,对创建的线程有两种操作:等待/分离,也就是join/detach操作。

join() 的意思是:父线程等待子线程结束,并回收资源;
detach() 的含义是:父线程和子线程相互分离,即使父线程结束了,只要主线程没有结束,子线程就会继续正常运行。如果父线程就是主线程,主线程结束,子线程也会结束。可能存在的问题:若子线程还没来得及清理垃圾的话,主线程就结束了,那么就会导致内存溢出。

join是等待被创建线程的结束之后回收它的资源。因此,join的调用位置就比较关键。两种解决方式,防止join阻塞或者资源泄露:
a.主动让被创建线程结束,如设置退出标志位等;
b.在对象的析构函数中调用线程的join函数,等待资源回收。

使用joinable()来判断join()可否调用。同样,detach()也只能调用一次,一旦detach()后就无法join()了,有趣的是,detach()可否调用也是使用joinable()来判断。

注意:使用detach()时,可能存在主线程比子线程先结束的情况,主线程结束后会释放掉自身的内存空间;在创建线程时,如果std::thread类传入的参数含有引用或指针,则子线程中的数据依赖于主线程中的内存,主线程结束后会释放掉自身的内存空间,则子线程会出现错误。

ps:因为c++11的多线程编程以boost库的多线程编程为基础,所以两者在用法上很相似

2.互斥量(锁)使用

2.1 什么是互斥量(锁)?

这样比喻:单位上有一台打印机(共享数据a),你要用打印机(线程1要操作数据a),同事老王也要用打印机(线程2也要操作数据a),但是打印机同一时间只能给一个人用,此时,规定不管是谁,在用打印机之前都要向领导申请许可证(lock),用完后再向领导归还许可证(unlock),许可证总共只有一个,没有许可证的人就等着在用打印机的同事用完后才能申请许可证(阻塞,线程1lock互斥量后其他线程就无法lock,只能等线程1unlock后,其他线程才能lock)。那么,打印机就是共享数据,访问打印机的这段代码就是临界区,这个必须互斥使用的许可证就是互斥量(锁)

互斥量是为了解决数据共享过程中可能存在的访问冲突的问题。这里的互斥量保证了使用打印机这一过程不被打断。

互斥量:采用互斥对象机制。只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。

2.2 互斥量(锁)怎么使用?

首先需要#include;(std::mutex和std::lock_guard都在头文件中声明。)

然后需要实例化std::mutex对象;

需要在进入临界区之前对互斥量加锁,退出临界区时对互斥量解锁;

lock()与unlock():

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;//实例化m对象,不要理解为定义变量
void proc1(int a)
{
    m.lock();
    cout << "proc1函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 2 << endl;
    m.unlock();
}

void proc2(int a)
{
    m.lock();
    cout << "proc2函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 1 << endl;
    m.unlock();
}
int main()
{
    int a = 0;
    thread t1(proc1, a);
    thread t2(proc2, a);
    t1.join();
    t2.join();
    return 0;
}

程序实例化mutex对象m,本线程调用成员函数m.lock()会发生下面 2 种情况: (1)如果该互斥量当前未上锁,则本线程将该互斥量锁住,直到调用unlock()之前,本线程一直拥有该锁。 (2)如果该互斥量当前被其他线程锁住,则本线程被阻塞,直至该互斥量被其他线程解锁,此时本线程将该互斥量锁住,直到调用unlock()之前,本线程一直拥有该锁。

不推荐实直接去调用成员函数lock(),因为如果忘记unlock(),将导致锁无法释放,使用lock_guard或者unique_lock则能避免忘记解锁带来的问题。

2.3 lock_guard

std::lock_guard()是什么呢?它就像一个保姆,职责就是帮你管理互斥量,就好像小孩要玩玩具时候,保姆就帮忙把玩具找出来,孩子不玩了,保姆就把玩具收纳好。

其原理是:声明一个局部的std::lock_guard对象,在其构造函数中进行加锁,在其析构函数中进行解锁。最终的结果就是:创建即加锁,作用域{ }结束自动解锁。从而使用std::lock_guard()就可以替代lock()与unlock()。

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;//实例化m对象,不要理解为定义变量
void proc1(int a)
{
    lock_guard<mutex> g1(m);//用此语句替换了m.lock();lock_guard传入一个参数时,该参数为互斥量,此时调用了lock_guard的构造函数,申请锁定m
    cout << "proc1函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 2 << endl;
}//此时不需要写m.unlock(),g1出了作用域被释放,自动调用析构函数,于是m被解锁

void proc2(int a)
{
    {
        lock_guard<mutex> g2(m);
        cout << "proc2函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 1 << endl;
    }//通过使用{}来调整作用域范围,可使得m在合适的地方被解锁
    cout << "作用域外的内容3" << endl;
    cout << "作用域外的内容4" << endl;
    cout << "作用域外的内容5" << endl;
}
int main()
{
    int a = 0;
    thread t1(proc1, a);
    thread t2(proc2, a);
    t1.join();
    t2.join();
    return 0;
}

2.4 unique_lock

std::unique_lock类似于lock_guard,只是std::unique_lock用法更加丰富,同时支持std::lock_guard()的原有功能。 使用std::lock_guard后不能手动lock()与手动unlock();使用std::unique_lock后可以手动lock()与手动unlock();

3. 条件变量

3.1如何使用?

头文件<condition_variable>针对条件变量提供了两个class,分别为condition_variable和condition_variable_any
condition_variable

std::condition_variable类搭配std::mutex类来使用,std::condition_variable对象(std::condition_variable cond;)的作用不是用来管理互斥量的,它的作用是用来同步线程,它的用法相当于编程中常见的flag标志(A、B两个人约定flag=true为行动号角,默认flag为false,A不断的检查flag的值,只要B将flag修改为true,A就开始行动)。

类比到std::condition_variable,A、B两个人约定notify_one为行动号角,A就等着(调用wait(),阻塞),只要B一调用notify_one,A就开始行动(不再阻塞)。

3.2 condition_variable

condition_variable用来唤醒一个或多个等待在某特定条件上的线程,下面列出了condition_variable提供的操作:所有等待(wait)某个条件的线程都必须使用相同的mutex,且必须使用unique_lock绑定mutex,并且让wait()等待在unique_lock上,否则会发生不明确的行为.
在这里插入图片描述

#include <iostream>                // std::cout
#include <thread>                // std::thread
#include <mutex>                // std::mutex, std::unique_lock
#include <condition_variable>    // std::condition_variable
 
std::mutex mtx; // 全局互斥锁.
std::condition_variable cond_; // 全局条件变量.
bool ready = false; // 全局标志位.
 
void do_print_id(int id)
{
    std::unique_lock <std::mutex> lck(mtx);
    while (!ready) // 如果标志位不为 true, 则等待...
        cond_.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,
    // 线程被唤醒, 继续往下执行打印线程编号id.
    std::cout << "thread " << id << '\n';
}
 
void go()
{
    std::unique_lock <std::mutex> lck(mtx);
    ready = true; // 设置全局标志位为 true.
    cond_.notify_all(); // 唤醒所有等待的线程. notify_one():唤醒一个等待的线程
}
 
int main()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(do_print_id, i);
 
    std::cout << "10 threads ready to race...\n";
    go(); // go!
 
  for (auto & th:threads)
        th.join();
 
    return 0;
}

3.3 condition_variable_any

condition_variable_any提供的操作与condition_variable类似(见上图),但是不提供native_handle()和notify_all_at_thread_exit()
condition_variable_any不要求使用std::unique_lock对象当做lock

4. 原子类型atomic<>

原子操作指“不可分割的操作”,也就是说这种操作状态要么是完成的,要么是没完成的,不存在“操作完成了一半”这种状况。互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量(操作变量时加锁防止他人干扰)。 std::atomic<>是一个模板类,使用该模板类实例化的对象,提供了一些保证原子性的成员函数来实现共享数据的常用操作。

可以这样理解: 在以前,定义了一个共享的变量(int i=0),多个线程会用到这个变量,那么每次操作这个变量时,都需要lock加锁,操作完毕unlock解锁,以保证线程之间不会冲突;但是这样每次加锁解锁、加锁解锁就显得很麻烦,那怎么办呢? 现在,实例化了一个类对象(std::atomic I=0)来代替以前的那个变量(这里的对象I你就把它看作一个变量,看作对象反而难以理解了),每次操作这个对象时,就不用lock与unlock,这个对象自身就具有原子性(相当于加锁解锁操作不用你写代码实现,能自动加锁解锁了),以保证线程之间不会冲突。

提到std::atomic<>,你脑海里就想到一点就可以了:std::atomic<>用来定义一个自动加锁解锁的共享变量(“定义”“变量”用词在这里是不准确的,但是更加贴切它的实际功能),供多个线程访问而不发生冲突。

//原子类型的简单使用
std::atomic<bool> b(true);
b=false;

参考资源:
1.C++多线程并发基础入门教程
2.C++ 标准库 条件变量:condition_variable
3.C++11并发编程-条件变量(condition_variable)详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/106464.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

你知道什么是 @Component 注解的派生性吗?

对于 Component 注解在日常的工作中相信很多小伙伴都会使用到&#xff0c;作为一种 Spring 容器托管的通用模式组件&#xff0c;任何被 Component 注解标注的组件都会被 Spring 容器扫描。 那么有的小伙伴就要问了&#xff0c;很多时候我们并没有直接写 Component 注解呀&…

计算机毕设Python+Vue寻人系统设计(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Spring Boot日志文件

哈喽呀&#xff0c;你好呀&#xff0c;欢迎呀&#xff0c;快来看一下这篇宝藏博客吧~~~ 目录 1.日志快速扫盲 2.Spring Boot项目日志简单分析 3.自定义打印日志 4.通过设置日志的级别来筛选和控制日志输出的内容 5.日志持久化 1.日志快速扫盲 什么是日志?说白了就是控制…

Java学习笔记——Idea集成git

Idea集成git-创建本地仓库-提交代码

深入浅出pom.xml文件

前言 在每一个pom文件的开头都会有这样几行代码 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocatio…

Spring Boot3.0正式发布及新特性解读

Spring Boot 3.0 正式发布 同时发布更新的还有 2.7.x 和 2.6.x 两条版本线&#xff0c;Spring Boot 是我见过的发版最守时的技术框架之一。 Spring Boot 3.0 现已正式发布&#xff0c;它包含了 12 个月以来 151 个开发者的 5700 多次代码提交。这是自 4.5 年前发布 2.0 以来&a…

Live800:在线客服系统排名是怎么样的?

在线客服系统排名是怎么样的?在线客服系统提供商提供哪些服务?在线客服系统评测要点?这些都是企业很关心的问题,这里进行简要的解答。 在线客服系统排名是怎么样的? 客观来说在线客服系统没有统一的行业标准,因此也没有统一的排名。各在线客服系统厂商各有特色,行业竞争激…

【大数据】python连接并使用redis

文章目录redis安装redis连接python安装redis库conda下载及配置vs连接redisredis使用stringset设置getrange截取append追加内容strlen(key) 字节长度listlpush,rpush赋值lpushx,rpushx只给存在的键值赋值llen 列表个数linsert 在某一个值前或者后插入新值lset 对某一个索引位置赋…

Hive 源码解读 准备篇 Debug 讲解

使用 Hive 执行 HQL 查询时遇到 bug,解决办法无非几种,explain HQL、查看日志、远程 Debug,本文就将详细讲解如何使用 Idea 远程 Debug。 1. Debug 环境准备 下载 Hive 源码包,自行编译一下,建议在 Linux 环境下编译,然后将整个编译好的包全部拷贝到 IDEA 工作目录中并…

软考高级-系统架构设计师-知识点总结(一)架构设计基础

第一部分&#xff0c;架构设计基础。由系统架构设计师概述、计算机与网络基础知识、信息系统基础知识、系统开发基础知识四部分构成。 目录 系统架构设计师概述 系统架构的概念和历史 系统架构设计师的定义 系统架构师具备的能力 计算机与网络基础知识 操作系统基础 操作…

加法扩散模型全部过程推导和实现代码

🍿*★,*:.☆欢迎您/$:*.★* 🍿 add_diff 使用之前的扩散方法(get_image_by_t_cv)总结出来的 get_noise 和 get_x 通过 add_diff 可以得到 通过get_x 带入 两组参数 可以推导出 get_xt_1 使用add_diff(这里要反着用 参考show_add_diff_r) 输入 x noise t t max 可以得到x…

DockeFile的介绍与使用

目录 1. Dockfile是什么 2. Dockerfile的基本组成 2.1 FROM 2.2 MAINTAINER 2.3 RUN 2.4 COPY 2.5 ADD 2.6 EXPOSE 2.7 WORKDIR 2.8 ONBUILD 2.9 USER 2.10 VOLUME 2.11 CMD 2.12 ENTRYPOINT 3. dockerfile示例 3.1 准备 3.2 将该目录上传至linux 3.3 构建镜…

Docker+Jenkins+Gitee+Maven项目配置jdk、maven、gitee等拉取代码并自动构建以及遇到的那些坑

场景 CentOS中使用Docker安装Jenkins&#xff1a; CentOS中使用Docker安装Jenkins_霸道流氓气质的博客-CSDN博客_centos docker jenkins 在上面使用Docker部署起来Jenkins的基础上&#xff0c;怎样拉取SpringBoot项目代码并编译构建。 后台项目的搭建参考如下。 若依前后端…

OpenFeign AutoConfiguration源码解析

本文约2千字&#xff0c;主要知识 OpenFeign的父子容器FeignClient的注册 背景 在使用Spring Cloud时&#xff0c;经常使用OpenFeign 作为远程服务调用的类库&#xff1b;Feign 是一种声明式服务调用组件&#xff0c;它在 RestTemplate 的基础上做了进一步的封装。通过 Feig…

跨平台应用开发进阶(五十)uni-app ios web-view嵌套H5项目白屏问题分析及解决

文章目录一、前言二、问题分析三、解决方案3.1 nvue 页面替代 vue 页面3.2 白屏检测刷新3.2.1 自动刷新3.2.2 手动刷新3.3 总结四、拓展阅读一、前言 应用uni-app框架开发好APP上架使用过程中&#xff0c;发现应用经过长时间由后台切换至前台时&#xff0c;通过webview方式嵌套…

SQL语句(基本)

SELECT 语句的 执行过程&#xff1a; from clause ---> where clause ---> select --->group by ---> having--->order by ---> limit 写法顺序: select col1,... from clause ---> where clause ---> group by ---> having---> order by --->…

“ 请你要发光 而不是被照亮 “

做一个厉害的大人 勇敢地长大 成为会发光的星星 勇音频&#xff1a;00:0003:41 | 01 | 世界不会辜负努力拼搏的人 光明的前途在乌云散去之后 请你一定一定坚持自己 勿忘初心 要做会发光的星星 成为想成为的大人啊 | 02 | 我牵起你的手 你望向我的眼 少了你的懵懂青涩…

总线一:IIC

一、I2C集成电路总线, 多用于主控制器和从器件间的主从通信。 二、适用场景&#xff1a;在小数据量场合使用&#xff0c;传输距离短。 三、IIC是半双工。IIC的物理层&#xff1a;两条总线线路&#xff0c;一条是串行数据线SDA&#xff0c;一条是串行时钟线SCL&#xff0c;当总…

《Python知识手册》更新到V4.1版,快拿走学习

前言 最近&#xff0c;我花了点时间&#xff0c;把《Python知识手册》的部分内容进行了更新&#xff0c;更新后的版本号为 v4.1 版。 python知识手册内容&#xff1a;《Python知识手册》 没有比较完整的覆盖 Python 的基础知识。因此&#xff0c;针对手册的阅读&#xff0c;各…

程序员年底好找工作吗?

到年底了除非必要不要辞职&#xff01;除非必要不要辞职&#xff01;除非必要不要辞职&#xff01; 重要的事情说三遍。 很多老哥问我&#xff1a;工作干不下去了&#xff0c;这会儿辞职找工作合适吗&#xff1f;今天就来为大家解答一下&#xff0c;为什么不要在年底辞职&…