C++11线程

C++11提供了线程库,下面我们来看一下如何使用。

线程的创建

头文件

要创建一个线程需要包一个线程头文件:#include <thread>

我们先来看看thread支持的构造方式。

支持默认构造,直接使用thread创建一个空的线程对象。

也支持带参的构造,参数就是可执行的对象,可以使函数指针、仿函数、lamdba表达式、包装器,参数是可执行对象需要传的参数,因为是可变参数,因此根据实际的可执行对象传递参数个数即可。

线程是不支持拷贝构造的,不可以用一个线程对象拷贝另一个线程对象,但是支持移动构造。

无参构造 + 移动赋值

我们创建线程的时候可以先不给该线程关联函数,创建一个空线程,等到后续有需要的时候在关联函数,比如实现线程池就可以这样做,线程池的线程是不知道要执行啥函数的。

直接使用 thread 这个类型,thread是一个类,里面实现了线程的各种方法,使用thread然后后面跟上变量名,这样创建的线程是没有启动的线程,还没有给该线程关联要执行的函数,可以使用移动赋值来给该线程关联要执行的函数,使用匿名构造,第一个参数传递要执行的函数,后续的参数是函数要传递的参数,如果函数有一个参数就传递1个有两个参数就传递2个。线程的构造函数是一个函数对象和可变参数列表。

我们创建好线程之后需要进行等待,直接使用thread的join函数即可,该函数没有返回值和参数。

get_id()可以获取线程的id,这个函数在this_thread这个命名空间里。因此使用时需要使用这个空间域。

#include <iostream>
#include <thread>
using namespace std;void func(int x)
{cout << this_thread::get_id() << endl;for(int i = 1; i <= x; i++)cout << "music" << endl;
}
int main()
{// 无参构造 + 移动赋值thread t1; // 没启动的线程t1 = thread(func, 9);// 线程等待t1.join();return 0;
}

总结:

1.带参构造,创建可执行对象

2.创建空线程对象,移动构造或者移动赋值,把右值线程对象转移过去 

带参构造

第二种方式是直接在创建线程的时候就给该线程关联上要执行的函数

#include <iostream>
#include <thread>
using namespace std;void func(int x)
{cout << this_thread::get_id() << endl;for(int i = 1; i <= x; i++)cout << "music" << endl;
}
int main()
{// 2.带参构造thread t2(func, 9);// 线程等待t2.join();return 0;
}

移动构造

第三种方式是使用移动构造

#include <iostream>
#include <thread>
using namespace std;void func(int x)
{cout << this_thread::get_id() << endl;for(int i = 1; i <= x; i++)cout << "music" << endl;
}
int main()
{// 3.移动构造thread t3 = thread(func, 9);// 线程等待t3.join();return 0;
}

ref

如果我们现在要使用两个线程对一个局部变量进行++操作,那么我们应该如何写代码呢?我们肯定是创建两个线程,然后把该局部变量以引用的方式传递过去,这样线程执行的函数就会修改我们传递的局部变量,这里当然是有线程安全问题需要加锁,但我们先不考虑加锁,我们看下面的代码。

#include <iostream>
#include <thread>
using namespace std;void add(int& x)
{for (int i = 0; i < 100; i++){x++;}
}int main()
{int num = 0;// 错误写法thread t1(add, num);// 这样写会报错,看似是引用的方式接收,但实际上接收的是num的拷贝// 正确写法thread t1(add, ref(num)); // 如果是引用方式接收,需要使用ref转一下,这样写才正确// 错误写法thread t2(add, num);// 这样写会报错,看似是引用的方式接收,但实际上接收的是num的拷贝// 正确写法thread t2(add, ref(num));// 如果是引用方式接收,需要使用ref转一下,这样写才正确this_thread::sleep_for(chrono::seconds(2));cout << num << endl;t1.join();t2.join();return 0;
}

上面的代码看上去是正确的,但是实际我们编译的时候发现编译不过去,原因在于add的参数是引用的方式接收的,但是由于num是先传给了t1的构造函数,然后在给add传递过去,相当于不是直接传过去,而是中间转了一层,所以这里看似是传递的num但实际传递的是num的拷贝,因此如果要传递num的引用,需要加上ref()。

休眠可以使用this_thread::sleep_for这个函数,chrono是时钟的意思,seconds是按秒休眠,也可以按毫秒milliseconds休眠。

mutex

这里毫无疑问是有线程安全的问题的,因此是需要加锁的。

要使用锁需要包含头文件,#include <mutex>

然后直接就可以使用mutex定义一把锁,如果要加锁调用lock方法,解锁调用unlock方法即可。

此时的代码就可以这样更改。

void add(mutex& mtx, int& x)
{for (int i = 0; i < 100; i++){mtx.lock();x++;mtx.unlock();}
}int main()
{int num = 0;mutex mt; //定义一把锁thread t1(add, ref(mt), ref(num));thread t2(add, ref(mt), ref(num));this_thread::sleep_for(chrono::seconds(2));cout << num << endl;t1.join();t2.join();return 0;
}

unique_lock和lock_guard

但是有的时候我们可能加锁的时候忘记解锁了,就会导致死锁,那么我们可以使用智能指针把锁管理起来,创建锁的时候直接加锁,出了作用域变量销毁在析构的时候直接解锁,此时我们就不需要手动的加锁解锁了,那么我们要自己实现吗?C++已经给我们提供了对锁进行管理的类,unique_lock和lock_guard,我们直接使用即可。

我们先来看unique_lock。

unique_lock是一个模板类,是专门用来管理所的类,类型直接传递锁的类型mutex即可,然后构造的时候把锁传递过去即可,一下是他的一些构造函数。

因此我们在线程函数里可以使用unique_lock,对传递过来的锁进行管理。

void add(mutex& mtx, int& x)
{for (int i = 0; i < 100; i++){unique_lock<mutex> lock(mtx);x++;}
}

lock_guard和unique_lock的使用方法是一样的。

void add(mutex& mtx, int& x)
{for (int i = 0; i < 100; i++){//unique_lock<mutex> lock(mtx);lock_guard<mutex> lock(mtx);x++;}
}

lock_guard相比unique_lock来说更轻量一些,unique_lock则更加灵活,可以支持延迟锁定、尝试锁定等,但开销会比较大。这两个都是基于RAII的,头文件都是<mutex>,它们两个可以用来配合条件变量。

atomic

我们加锁是因为对一个数++操作不是原子的,C++给我们们提供了atomic这个类,我们可以直接使用这个类定义变量,定义的变量++和--操作是原子的操作,此时就可以不需要加锁。

使用非常简单,atomic是一个类,如果要定义一个int类型的变量,直接模版参数传递int即可,比如说atomic<int> nums, 此时对nums++和--就是原子的操作,也可以对他进行打印和赋值,使用起来和int类型是一样的。

void add(mutex& mtx, atomic<int>& x)
{for (int i = 0; i < 100; i++){x++;}
}int main()
{atomic<int> num = 0;mutex mt;thread t1(add, ref(mt), ref(num));thread t2(add, ref(mt), ref(num));this_thread::sleep_for(chrono::seconds(2));cout << num << endl;t1.join();t2.join();return 0;
}

条件变量

头文件:#include <condition_variable>

要使用条件变量需要包含上面的头文件,创建条件变量直接使用condition_variable创建一个变量即可。

wait方法:将该线程加入到阻塞队列中

我们可以直接调用wait就可以将线程加入到阻塞队列,传递的参数是一个RAII的锁,也就是unique_lock管理的锁,加入到阻塞队列后锁会被释放。

 唤醒线程:将在阻塞队列的函数唤醒

唤醒线程有两个,一个是唤醒阻塞队列中的全部线程,一个是唤醒阻塞队列中的一个线程。这两个唤醒函数都不需要传递参数。

接下来我们用条件变量来实现一个两个线程交替打印奇偶数

两个线程交替打印奇偶数

首先要创建两个线程,当然也需要创建锁和条件变量,也需要一个变量number,number初始化为1,我们规定,让线程1先来打印number,线程1打印完毕后对number进行+1操作,此时number就变成了偶数,然后让线程2打印number,此时打印的就是偶数,然后+1,让线程1打印,如此交替执行即可。

既然要让线程1先打印,那么线程2一定要被阻塞住,否则是无法保证打印顺序的。一开始线程1打印,然后线程2被阻塞,线程1打印完毕后要唤醒线程2去打印,然后把自己阻塞住,线程2打印完毕后唤醒线程1,然后让自己阻塞,这样就可以控制打印顺序。

那么我们如何控制谁先打印和阻塞?我们可以使用一个flag标记。

刚开始flag是假,线程1的判断条件是while(flag),因此,一开始线程1不会被阻塞会直接打印,而线程2条件是while(!flag),会直接进入阻塞队列阻塞,当线程1打印完后,把flag置为true,然后唤醒线程2,线程2的判断条件不成立不会进入阻塞队列会打印线程1,然而线程1条件成立会进入阻塞队列,线程2打印完毕后把flag置为false,自己会进入阻塞队列,而线程1条件不成立会直接打印,此时就完成了打印顺序的控制。

当然我们也可以不需要加锁,用atomic创建变量num即可。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <condition_variable>using namespace std;
int main()
{int num = 1;bool flag = false;mutex mtx; // 锁condition_variable cond; // 条件变量thread t1([&]() {for (int i = 0; i < 50; ++i){unique_lock<mutex> lock(mtx);while (flag)cond.wait(lock);cout << "线程1:" << num << endl;num++;flag = true;cond.notify_one();}});thread t2([&]() {for (int i = 0; i < 50; i++){unique_lock<mutex> lock(mtx);while (!flag) cond.wait(lock);cout <<  "线程2:" << num << endl;num++;flag = false;cond.notify_one();}//std::this_thread::sleep_for(std::chrono::seconds(1));});t1.join();t2.join();return 0;
}

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

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

相关文章

Golang 并发机制-3:通道(channels)机制详解

并发编程是一种创建性能优化且响应迅速的软件的强大方法。Golang&#xff08;也称为 Go&#xff09;通过通道&#xff08;channels&#xff09;这一特性&#xff0c;能够可靠且优雅地实现并发通信。本文将揭示通道的概念&#xff0c;解释其在并发编程中的作用&#xff0c;并提供…

笔记:使用ST-LINK烧录STM32程序怎么样最方便?

一般板子在插件上&#xff0c; 8脚 3.3V;9脚 CLK;10脚 DIO;4脚GND ST_Link 19脚 3.3V;9脚 CLK;7脚 DIO;20脚 GND 烧录软件&#xff1a;ST-LINK Utility&#xff0c;Keil_5; ST_Link 接口针脚定义&#xff1a; 按定义连接ST_Link与电路板&#xff1b; 打开STM32 ST-LINK Uti…

网络测试工具

工具介绍&#xff1a; 这是一个功能完整的网络测速工具&#xff0c;可以测试网络的下载速度、上传速度和延迟。 功能特点&#xff1a; 1. 速度测试 - 下载速度测试 - 上传速度测试 - Ping延迟测试 - 自动选择最佳服务器 2. 实时显示 - 进度条显示测试进度 - 实时显示测试状…

java每日精进1.31(SpringSecurity)

在所有的开发的系统中&#xff0c;都必须做认证(authentication)和授权(authorization)&#xff0c;以保证系统的安全性。 一、基础使用 1.依赖 <dependencies><!-- 实现对 Spring MVC 的自动化配置 --><dependency><groupId>org.springframework.bo…

17.2 图形绘制8

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 17.2.10 重绘 先看以下例子&#xff1a; 【例 17.28】【项目&#xff1a;code17-028】绘制填充矩形。 private void button1_Clic…

自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数

import numpy as np import torch import torch.nn as nn import torch.optim as optim from sklearn.metrics import precision_score, recall_score, f1_score# 数据准备 class1_points np.array([[1.9, 1.2],[1.5, 2.1],[1.9, 0.5],[1.5, 0.9],[0.9, 1.2],[1.1, 1.7],[1.4,…

neo4j入门

文章目录 neo4j版本说明部署安装Mac部署docker部署 neo4j web工具使用数据结构图数据库VS关系数据库 neo4j neo4j官网Neo4j是用ava实现的开源NoSQL图数据库。Neo4作为图数据库中的代表产品&#xff0c;已经在众多的行业项目中进行了应用&#xff0c;如&#xff1a;网络管理&am…

Java基础——分层解耦——IOC和DI入门

目录 三层架构 Controller Service Dao ​编辑 调用过程 面向接口编程 分层解耦 耦合 内聚 软件设计原则 控制反转 依赖注入 Bean对象 如何将类产生的对象交给IOC容器管理&#xff1f; 容器怎样才能提供依赖的bean对象呢&#xff1f; 三层架构 Controller 控制…

智慧园区系统集成解决方案引领未来城市管理的智能化转型

内容概要 在现代城市管理的背景下&#xff0c;“智慧园区系统集成解决方案”正扮演着越来越重要的角色。这种解决方案不仅仅是技术上的创新&#xff0c;更是一种全新的管理理念&#xff0c;它旨在通过高效的数据整合与分析&#xff0c;优化资源配置&#xff0c;提升运营效率。…

99.24 金融难点通俗解释:MLF(中期借贷便利)vs LPR(贷款市场报价利率)

目录 0. 承前1. 什么是MLF&#xff1f;1.1 专业解释1.2 通俗解释1.3 MLF的三个关键点&#xff1a; 2. 什么是LPR&#xff1f;2.1 专业解释2.2 通俗解释2.3 LPR的三个关键点&#xff1a; 3. MLF和LPR的关系4. 传导机制4.1 第一步&#xff1a;央行调整MLF4.2 第二步&#xff1a;银…

【VM】VirtualBox安装CentOS8虚拟机

阅读本文前&#xff0c;请先根据 VirtualBox软件安装教程 安装VirtualBox虚拟机软件。 1. 下载centos8系统iso镜像 可以去两个地方下载&#xff0c;推荐跟随本文的操作用阿里云的镜像 centos官网&#xff1a;https://www.centos.org/download/阿里云镜像&#xff1a;http://…

家居EDI:Hom Furniture EDI需求分析

HOM Furniture 是一家成立于1977年的美国家具零售商&#xff0c;总部位于明尼苏达州。公司致力于提供高品质、时尚的家具和家居用品&#xff0c;满足各种家庭和办公需求。HOM Furniture 以广泛的产品线和优质的客户服务在市场上赢得了良好的口碑。公司经营的产品包括卧室、客厅…

【VUE案例练习】前端vue2+element-ui,后端nodo+express实现‘‘文件上传/删除‘‘功能

近期在做跟毕业设计相关的数据后台管理系统&#xff0c;其中的列表项展示有图片展示&#xff0c;添加/编辑功能有文件上传。 “文件上传/删除”也是我们平时开发会遇到的一个功能&#xff0c;这里分享个人的实现过程&#xff0c;与大家交流谈论~ 一、准备工作 本次案例使用的…

VLN视觉语言导航基础

0 概述 视觉语言导航模型旨在构建导航决策模型 π π π&#xff0c;在 t t t时刻&#xff0c;模型能够根据指令 W W W、历史轨迹 τ { V 1 , V 2 , . . . , V t − 1 } \tau\{V_1,V_2,...,V_{t-1}\} τ{V1​,V2​,...,Vt−1​}和当前观察 V t { P t , R t , N ( V t ) } V_…

Flux的三步炼丹炉——fluxgym(三):矩阵测试

前面两篇文章给大家介绍了如何准备素材和怎么炼丹&#xff0c;现在我们拿到训练完成后的多个Lora怎么才能确定哪个才是我们需要的、效果最好的呢&#xff1f;答案就是使用xyz图表测试&#xff0c;也称为矩阵测试&#xff0c;通过控制控制变量的方法对Lora模型批量生图&#xff…

利用Muduo库实现简单且健壮的Echo服务器

一、muduo网络库主要提供了两个类&#xff1a; TcpServer&#xff1a;用于编写服务器程序 TcpClient&#xff1a;用于编写客户端程序 二、三个重要的链接库&#xff1a; libmuduo_net、libmuduo_base、libpthread 三、muduo库底层就是epoll线程池&#xff0c;其好处是…

文件读写操作

写入文本文件 #include <iostream> #include <fstream>//ofstream类需要包含的头文件 using namespace std;void test01() {//1、包含头文件 fstream//2、创建流对象ofstream fout;/*3、指定打开方式&#xff1a;1.ios::out、ios::trunc 清除文件内容后打开2.ios:…

C++编程语言:抽象机制:模板(Bjarne Stroustrup)

目录 23.1 引言和概观(Introduction and Overview) 23.2 一个简单的字符串模板(A Simple String Template) 23.2.1 模板的定义(Defining a Template) 23.2.2 模板实例化(Template Instantiation) 23.3 类型检查(Type Checking) 23.3.1 类型等价(Type Equivalence) …

无人机图传模块 wfb-ng openipc-fpv,4G

openipc 的定位是为各种模块提供底层的驱动和linux最小系统&#xff0c;openipc 是采用buildroot系统编译而成&#xff0c;因此二次开发能力有点麻烦。为啥openipc 会用于无人机图传呢&#xff1f;因为openipc可以将现有的网络摄像头ip-camera模块直接利用起来&#xff0c;从而…

【JavaEE进阶】图书管理系统 - 壹

目录 &#x1f332;序言 &#x1f334;前端代码的引入 &#x1f38b;约定前后端交互接口 &#x1f6a9;接口定义 &#x1f343;后端服务器代码实现 &#x1f6a9;登录接口 &#x1f6a9;图书列表接口 &#x1f384;前端代码实现 &#x1f6a9;登录页面 &#x1f6a9;…