C++11的多线程

news2025/1/21 17:03:12

目录

引言

thread类的简单介绍

接口解读

使用范例

move的作用--将资源“夺舍”

原子性操作库(atomic)

lock_guard与unique_lock

前置知识:mutex锁(类似linux下的ptrhead_mutex_t数据)

mutex的种类

1. std::mutex

2. std::recursive_mutex

3. std::timed_mutex

4. std::recursive_timed_mutex

RAII

lock_guard

unique_lock

条件变量


引言

C++11的多线程特性,是对C++语言的一次重大补充,它使得C++在保持高性能和低级操作能力的同时,具备了更加强大的并发编程能力。在这个引言中,我们将探索C++11多线程的核心概念,包括线程的创建与管理、同步机制、数据共享与保护,以及异步操作等。这些特性不仅极大地简化了多线程程序的编写,也为C++开发者打开了一扇通往高效并发编程的大门。本文重点介绍:C++11的线程库部分,已经相对应的线程安全。

thread类的简单介绍

在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接
口,这使得代码的可移植性比较差C++11中最重要的特性就是对线程进行支持了,使得C++在
并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的
线程,必须包含< thread >头文件。

接口解读

thread类的第一个参数是一个可调用对象,后续的参数是可调用对象的参数

1.线程库禁止赋值拷贝、与赋值重载;但是允许移动构造!

两种构造,第二种是一种可变参数模板

2.以下接口:1.获取pid 2.是否线程可jion 3.阻塞join 4.分离 5.交换
3.thread和this_thread有什么区别?

在C++11中,thread 和 this_thread 是 <thread> 头文件中提供的两个不同的命名空间,它们各自包含了一系列用于多线程编程的函数和类。

以下是 thread 和 this_thread 的主要区别:

  1. std::thread

    • std::thread 是一个类,用于表示一个执行线程。
    • 它可以用来创建新的线程,并与之交互。
    • 通过构造函数,可以传递一个函数或者函数对象,以及可选的参数来启动线程。
    • std::thread 对象可以调用 join() 或 detach() 方法来同步或分离线程。
    • std::thread 类还提供了其他成员函数,如 get_id() 来获取线程ID,joinable() 来检查线程是否可以加入,以及 native_handle() 来获取底层实现特定的线程句柄。
  2. std::this_thread

    • std::this_thread 是一个命名空间,它包含了一系列函数,这些函数作用于当前执行的线程。
    • 它提供了如 get_id() 来获取当前线程的ID,yield() 来提示调度器当前线程愿意放弃处理器,以及 sleep_for() 和 sleep_until() 来使当前线程暂停执行指定的时间。
    • std::this_thread 中的函数通常用于控制或获取有关当前线程的信息,而不是创建或管理其他线程。

简而言之,std::thread 是用来创建和管理线程的类,而 std::this_thread 是一个包含用于操作当前执行线程的函数的命名空间。

使用范例

#include<iostream>
#include<vector>
#include<string>
#include<mutex>
#include<thread>
#include<chrono>

using namespace std;

void Print1(size_t n, const string& s, mutex& m, int& rx)
{
	for (size_t i = 0; i < n; i++)
	{
		m.lock();

		cout <<this_thread::get_id()<<s<<":" << i << endl;
		++rx;

		m.unlock();

		this_thread::sleep_for(chrono::milliseconds(300));
	}
}

int main()
{
	mutex mtx;
	int x = 0;
	thread t1(Print1, 2,  "xianchen1", ref(mtx), ref(x));
	thread t2(Print1, 3, "xiancheng2", ref(mtx), ref(x));

	//thread t3(t1);

	cout <<"线程1:" << t1.get_id() << endl;
	cout <<"线程2:"<< t2.get_id() << endl;

	t1.join();	//在此处阻塞
	t2.join();

	cout << x << endl;

	return 0;
}

注意点:

1.在线程的执行方法中,如果想要引用传参,必须要加上ref。保持引用的属性。

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在
线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参
我们可以理解为:数据-->值拷贝到线程栈--->在线程栈的线程方法中执行。
#include <thread>
void ThreadFunc1(int& x)
{
 x += 10;
}
void ThreadFunc2(int* x)
{
 *x += 10;
}
int main()
{
 int a = 10;
 // 在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际
引用的是线程栈中的拷贝
 thread t1(ThreadFunc1, a);
 t1.join();
 cout << a << endl;
 // 如果想要通过形参改变外部实参时,必须借助std::ref()函数
 thread t2(ThreadFunc1, std::ref(a);
 t2.join();
 cout << a << endl;
 // 地址的拷贝
 thread t3(ThreadFunc2, &a);
 t3.join();
 cout << a << endl;
 return 0;
}
注意:如果是类成员函数作为线程参数时,必须将this作为线程函数参数。

2.chrono类提供了时间管理方法。

例二:支持移动赋值。移动构造使得可以创建线程池(最开始全是空线程)。

void Print2(size_t n, const string& s)
{
	for (size_t i = 0; i < n; i++)
	{
		cout << this_thread::get_id() << s << ":" << i << endl;
	}
}

int main()
{
	size_t n;
	cin >> n;

	//创建n个线程执行Print(空线程)
	vector<thread> vthd(n);
	size_t j = 0;
	for (auto& thd : vthd)
	{
		// 移动赋值(线程启动)
		thd = thread(Print2, 10,  "线程" + to_string(j++));
	}

	for (auto& thd : vthd)
	{
		thd.join();
	}

	thread t1(Print1, 100, 1, "我是小明");
	thread t2(move(t1));

	t2.join();

	return 0;
}

赋值时的thread()是一个匿名对象,可以理解为将亡值。由于支持移动语义。因此可以进行“夺舍”。

直接转转不过去,但是move就可以。

thread t1(Print, 100, 1, "111");
thread t2(move(t1));
t2.join();

move的作用--将资源“夺舍”

在调用 std::move(t) 的那行代码中,t 被视为一个右值,你可以将它移动到一个新的对象中。但是,一旦移动操作完成,t 就变成了一个空的 std::thread 对象,它不再管理任何线程资源。如果在移动之后继续使用 t,可能会导致未定义行为,因为 t 可能不再指向任何有效的线程。

这个函数与上述的效果一致。

原子性操作库(atomic)

当对数据并发访问的时候,就需要对数据进行上锁保护。但是对于简单的数据操作,可以采用atomic类去封装数据。
多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问
题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数
据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。
C++98中传统的解决方式:可以对共享修改的数据可以加锁保护。
虽然加锁可以解决,但是加锁有一个缺陷就是:只要一个线程在对sum++时,其他线程就会被阻
塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。
因此C++11中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入
的原子操作类型,使得线程间数据的同步变得非常高效。
在C++11中,程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的
访问
更为普遍的,程序员可以使用atomic类模板,定义出需要的任意原子类型
原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11
中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及
operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算
符重载默认删除掉了。---防止资源抢占。
#include <atomic>
int main()
{
 atomic<int> a1(0);
 //atomic<int> a2(a1);   // 编译失败
 atomic<int> a2(0);
 //a2 = a1;               // 编译失败
 return 0;
}

这样进行a1++,就是调用的operator++。

lock_guardunique_lock

在多线程环境下,如果想要保证某个变量的安全性,只要将其设置成对应的原子类型即可,即高
效又不容易出现死锁问题。但是有些情况下,我们可能需要保证一段代码的安全性,那么就只能
通过锁的方式来进行控制。

前置知识:mutex锁(类似linux下的ptrhead_mutex_t数据)

// mutex example
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

std::mutex mtx;           // mutex for critical section

void print_block (int n, char c) {
  // critical section (exclusive access to std::cout signaled by locking mtx):
  mtx.lock();
  for (int i=0; i<n; ++i) { std::cout << c; }
  std::cout << '\n';
  mtx.unlock();
}

int main ()
{
  std::thread th1 (print_block,50,'*');
  std::thread th2 (print_block,50,'$');

  th1.join();
  th2.join();

  return 0;
}

mutex的种类

在C++11中,Mutex总共包了四个互斥量的种类:

1. std::mutex

        
注意,线程函数调用lock()时,可能会发生以下三种情况:
如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,
该线程一直拥有该锁
如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住
如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)(锁的重复申请lock)
线程函数调用try_lock()时,可能会发生以下三种情况:
如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock
释放互斥量
如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉
如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

2. std::recursive_mutex

这是递归锁。在递归函数中,如果对互斥量上锁,需要用递归锁

允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,
释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

3. std::timed_mutex

时间锁,支持在临界区休眠。

std::mutex 多了两个成员函数,try_lock_for()try_lock_until()

4. std::recursive_timed_mutex

RAII

当我们对数据不好控制时,特别是两把锁,容易产生死锁,因此在锁锁在的作用域中,我们期待出作用域直接销毁。这就需要RAII。

lock_guard

只有构造和析构

lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封
,在需要加锁的地方,只需要用上述介绍的任意互斥体实例化一个lock_guard,调用构造函数
成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁
问题
lock_guard的缺陷:太单一,用户没有办法对该锁进行控制,因此C++11又提供了unique_lock。

unique_lock

相较于lock_guard,它最大的区别就是:

1.支持手动解锁

2.支持更多的锁类型,因此出现了这些接口

条件变量

由于存在线程同步机制,因此C++库也更新了条件变量。

行为上仍然类似于POSIX条件变量。

其中notify_one为唤醒一个线程,notify_all为全部唤醒。

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

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

相关文章

电子应用设计方案96:智能AI充电器系统设计

智能 AI 充电器系统设计 一、引言 智能 AI 充电器系统旨在为各种电子设备提供高效、安全、智能的充电解决方案&#xff0c;通过融合人工智能技术&#xff0c;实现自适应充电、优化充电效率和保护电池寿命。 二、系统概述 1. 系统目标 - 自适应识别不同设备的充电需求&#xf…

logback日志自定义占位符

前言 在大型系统运维中&#xff0c;很大程度上是需要依赖日志的。在java大型web工程中&#xff0c;一般都会使用slf4jlogback这一个组合来实现日志的管理。 logback中很多现成的占位符可以可以直接使用&#xff0c;比如线程号【%t】、时间【%d】、日志等级【%p】&#xff0c;…

Stable Diffusion 3.5 模型在 Linux 上的部署指南

文章目录 前言-参考资料如下一. ComfyUI安装二.模型下载2.1 安装GGUF和T5 xxl编码模型2.2 安装ComfyUI辅助插件2.3 启动ComfyUI2.4 基础ComfyUI和SD3.5配置2.5 demo 前言-参考资料如下 ComfyUI WIKI教程 sd3.5 github 尝试过sd集成ollama&#xff0c;但是sd在ollama上无法良好…

【Go】Go数据类型详解—指针

1. 前言 在我看来&#xff0c;一门编程语言语法的核心就在于数据类型。而各类编程语言的基本数据类型大致相同&#xff1a;int整型、float浮点型、string字符串类型、bool布尔类型&#xff0c;但是在一些进阶数据类型上就有所不同了。本文将会介绍Go语言当中核心的数据类型——…

Grafana 统一可视化了,告警如何统一?

对于大部分公司&#xff0c;通常都不止一套监控、可观测性相关的系统&#xff0c;云上的、云下的&#xff0c;开源的、商业的&#xff0c;指标的、日志的、链路的&#xff0c;各个系统体验不同&#xff0c;权限难管&#xff0c;如何统一化并为各个团队赋能&#xff0c;是很多技…

LeetCode 110.平衡二叉树

题目描述 给定一个二叉树&#xff0c;判断它是否是平衡二叉树。 示例 1&#xff1a; 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,3,3,null,null,4,4] 输出&#xff1a;false 示例 3&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;true 提示&#xff1a; …

【银河麒麟高级服务器操作系统】业务访问慢网卡丢包现象分析及处理过程

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;product.kylinos.cn 开发者专区&#xff1a;developer.kylinos.cn 文档中心&#xff1a;document.kylinos.cn 交流论坛&#xff1a;forum.kylinos.cn 服务器环境以及配置 【内核版本…

软件测试—— 接口测试(HTTP和HTTPS)

软件测试—— 接口测试&#xff08;HTTP和HTTPS&#xff09; HTTP请求方法GET特点使用场景URL结构URL组成部分URL编码总结 POST特点使用场景请求结构示例 请求标头和响应标头请求标头&#xff08;Request Headers&#xff09;示例请求标头 响应标头&#xff08;Response Header…

OpenCV相机标定与3D重建(60)用于立体校正的函数stereoRectify()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 为已校准的立体相机的每个头计算校正变换。 cv::stereoRectify 是 OpenCV 中用于立体校正的函数&#xff0c;它基于已知的相机参数和相对位置&am…

ARP 表、MAC 表、路由表、跨网段 ARP

文章目录 一、ARP 表1、PC2、路由器 - AR22203、交换机 - S57004、什么样的设备会有 ARP 表&#xff1f; 二、MAC 表什么样的设备会有 MAC 表&#xff1f; 三、路由表什么样的设备会有路由表&#xff1f; 四、抓取跨网段 ARP 包 所谓 “透明” 就是指不用做任何配置 一、ARP 表…

深度学习 · 手撕 DeepLearning4J ,用Java实现手写数字识别 (附UI效果展示)

引言 随着人工智能技术的不断发展&#xff0c;手写数字识别已经成为深度学习领域的一个经典案例。不管是老牌的机器学习模型还是现代的神经网络架构&#xff0c;手写数字识别总是大家学习和实战的起点之一。而对于我们日常使用的Java开发者来说&#xff0c;借助DeepLearning4J…

天机学堂5-XxlJobRedis

文章目录 梳理前面的实现&#xff1a;Feign点赞改进 day07-积分系统bitmap相关命令签到增加签到记录计算本月已连续签到的天数查询签到记录 积分表设计签到-->发送RabbitMQ消息&#xff0c;保存积分对应的消费者&#xff1a;**消费消息 用于保存积分**增加积分查询个人今日积…

2024 年度学习总结

目录 1. 前言 2. csdn 对于我的意义 3. 写博客的初衷 3.1 现在的想法 4. 写博客的意义 5. 关于生活和博客创作 5.1 写博客较于纸质笔记的优势 6. 致 2025 1. 前言 不知不觉, 来到 csdn 已经快一年了, 在这一年中, 我通过 csdn 学习到了很多知识, 结识了很多的良师益友…

使用Chrome和Selenium实现对Superset等私域网站的截图

最近遇到了一个问题&#xff0c;因为一些原因&#xff0c;我搭建的一个 Superset 的 Report 功能由于节假日期间不好控制邮件的发送&#xff0c;所以急需一个方案来替换掉 Superset 的 Report 功能 首先我们需要 Chrome 浏览器和 Chrome Driver&#xff0c;这是执行数据抓取的…

[操作系统] 进程的调度

进程切换概念 时间⽚&#xff1a;当代计算机都是分时操作系统&#xff0c;没有进程都有它合适的时间⽚(其实就是⼀个计数 器)。时间⽚到达&#xff0c;进程就被操作系统从CPU中剥离下来。 死循环是如何运行&#xff1f; 当一个进程代码为死循环&#xff0c;它并不会一直占据C…

Biotin sulfo-N-hydroxysuccinimide ester ;生物素磺基-N-羟基琥珀酰亚胺酯;生物素衍生物;190598-55-1

一、生物素及其衍生物的概述 生物素衍生物是指在生物素&#xff08;Vitamin H或B7&#xff09;分子基础上进行化学修饰得到的衍生化合物。这些衍生化合物在生物医学研究、临床诊断和药物开发等领域有着广泛的应用。 生物素&#xff08;Biotin&#xff09;是一种水溶性维生素&a…

Jenkins-Pipeline简述

一. 什么是Jenkins pipeline&#xff1a; pipeline在jenkins中是一套插件&#xff0c;主要功能在于&#xff0c;将原本独立运行于单个或者多个节点的任务连接起来&#xff0c;实现单个任务难以完成的复杂发布流程。Pipeline的实现方式是一套Groovy DSL&#xff0c;任何发布流程…

Linux系统下安装配置Nginx(保姆级教程)

目录 前言 安装配置Nginx 一.下载依赖 二.下载Nginx 1. 访问官网?&#xff0c;获取需要的Nginx版本 2. 将文件下载到Linux系统 3. 解压文件 4. 解压成功后&#xff0c;当前文件夹会出现一个nginx-1.26.1文件夹&#xff0c;进入到文件夹内 5. 配置nginx 6.?编译并安…

《Linux服务与安全管理》| 邮件服务器安装和配置

《Linux服务与安全管理》| 邮件服务器安装和配置 目录 《Linux服务与安全管理》| 邮件服务器安装和配置 1.在Server01上安装dns、postfix、dovecot和telnet&#xff0c;并启动 2&#xff0e;在Server01上配置DNS服务器&#xff0c;设置MX资源记录 3&#xff0e;在server1上…

WPS数据分析000001

目录 一、表格的新建、保存、协作和分享 新建 保存 协作 二、认识WPS表格界面 三、认识WPS表格选项卡 开始选项卡 插入选项卡 页面布局选项卡 公式选项卡 数据选项卡 审阅选项卡 视图选项卡 会员专享选项卡 一、表格的新建、保存、协作和分享 新建 ctrlN------…