【C++】多线程

目录

一 概念

1 多线程

2 多进程与多线程

3 多线程理解

二 创建线程

1 thread

2 join() 和 detach()

3 this_thread

三 std::mutex

1 lock 和 unlock

2 lock_guard

3 unique_lock 

四 condition_variable

五 std::atomic


一 概念

1 多线程

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

C++11引入了对多线程编程的支持,主要提供了以下几个组件:

  1. std::thread类:用于创建和管理线程的对象。
  2. std::mutex类:用于实现互斥访问,保护共享资源的完整性。
  3. std::condition_variable类:用于线程间的条件同步。
  4. std::atomic模板类:用于实现原子操作,确保数据的原子性

2 多进程与多线程

进程是指一个程序的运行实例,而线程是指进程中独立的执行流程。一个进程可以有多个线程,多个线程之间可以并发执行。

  • 一个程序有且只有一个进程,但可以拥有至少一个的线程。
  • 不同进程拥有不同的地址空间,互不相关,而不同线程共同拥有相同进程的地址空间。

创建一个新线程的代价要比创建一个新进程小得多 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多 线程占用的资源要比进程少很多

3 多线程理解

二 创建线程

1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的 状态。

2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。

3. C++支持多线程编程,主要使用的是线程库<thread>

1 thread

示例1: 线程函数(函数指针)

#include<iostream>
#include<thread>
using namespace std;

void Print1(int n, int j)
{
	cout << "n: " << n << " j: " << j << endl;
}

void Print2()
{
	cout << "Test2" << endl;
}

int main()
{
	// 线程函数为函数指针
	thread t1(Print1, 100, 5);
	thread t2(Print2);

	t1.join();
	t2.join();

	return 0;
}

上述示例中,我们创建了两个线程t1t2,使用函数Print1()和Print2()作为线程的执行函数,并使用join()函数等待线程执行完成。 

示例2: 执行函数有引用参数

void Print(int n, int& rx)
{
	rx += n;
	cout << "Test: " << rx << endl;
}

int main()
{
	int x = 0;


	thread t1(Print, 10000, ref(x));// 只有添加 ref 才能会被底层认为是引用
	thread t2(Print, 20000, ref(x));

	t1.join();
	t2.join();

	cout << x << endl;

	return 0;
}

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

 示例3: lambda 表达式

int main()
{
	int x = 0;

	auto Func = [&](int n)
	{
		x += n;
	};

	thread t1(Func, 10000);
	thread t2(Func, 20000);

	t1.join();
	t2.join();

	cout << x << endl;
	return 0;
}

2 join() 和 detach()

当线程启动后,一定要在和线程相关联的thread销毁前,确定以何种方式等待线程执行结束。比如上例中的join。

  • detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。
  • join方式,等待启动的线程完成,才会继续往下执行。

 示例1: join

oin后面的代码不会被执行,除非子线程结束

void thread_1()
{
    while (1)
    {}
}
void thread_2(int x)
{
    while (1)
    {}
}
int main()
{
    thread first(thread_1); // 开启线程,调用:thread_1()
    thread second(thread_2, 100); // 开启线程,调用:thread_2(100)

    first.join();
    second.join(); //join完了之后,才能往下执行。
    while (1)
    {
        std::cout << "主线程\n";
    }
    return 0;
}

示例2: detach

 主线程不会等待子线程结束。如果主线程运行结束,程序则结束。

void thread_1()
{
    while (1)
    {
        cout << "子线程1" << endl;
    }
}

void thread_2(int x)
{
    while (1)
    {
        cout << "子线程2" << endl;
    }
}

int main()
{
    thread first(thread_1);  // 开启线程,调用:thread_1()
    thread second(thread_2, 100); // 开启线程,调用:thread_2(100)

    first.detach();
    second.detach();
    for (int i = 0; i < 10; i++)
    {
        std::cout << "主线程\n";
    }
    return 0;

注意:

1. 线程是在thread对象被定义的时候开始执行的,而不是在调用join()函数时才执行的,调用join()函数只是阻塞等待线程结束并回收资源。
2. 分离的线程(执行过detach()的线程)会在调用它的线程结束或自己结束时自动释放资源。
3. 线程会在函数运行完毕后自动释放,不推荐利用其他方法强制结束线程,可能会因资源未释放而导致内存泄漏。
4. 若没有执行join()或detach()的线程在程序结束时会引发异常

3 this_thread

this_thread是一个类,它有4个功能函数,具体如下:

 此外,this_thread还包含重载运算符==!=,用于比较两个线程是否相等。

void my_thread()
{
	std::cout << "Thread " << std::this_thread::get_id() << " start!" << std::endl;
	std::this_thread::yield();	// 让出当前线程的时间片
	std::this_thread::sleep_for(std::chrono::milliseconds(200));  // 线程休眠200毫秒
	std::cout << "Thread " << std::this_thread::get_id() << " end!" << std::endl;
}

int main()
{
	std::cout << "Main thread id: " << std::this_thread::get_id() << std::endl;

	std::thread t1(my_thread);
	std::thread t2(my_thread);

	t1.join();
	t2.join();
	return 0;
}

thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个 线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。

可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效

a 采用无参构造函数构造的线程对象  b 线程对象的状态已经转移给其他线程对象  c 线程已经调用jion或者detach结束

 并发与并行的区别?

并发和并行都可以是调用了很多线程. 如果这些线程能同时被多个处理器执行,那就是并行的;如果是轮流切换执行,那就是并发.

三 std::mutex

在多线程编程中,需要注意以下问题:

  • 线程之间的共享数据访问需要进行同步,以防止数据竞争和其他问题。可以使用互斥量条件变量等机制进行同步。
  • 可能会发生死锁问题,即多个线程互相等待对方释放锁,导致程序无法继续执行。
  • 可能会发生竞态条件问题,即多个线程执行的顺序导致结果的不确定性。

mutex头文件主要声明了与互斥量(mutex)相关的类。mutex提供了4种互斥类型,如下表所示。

1 lock 和 unlock

std::mutex是 C++11 中最基本的互斥量,一个线程将mutex锁住时,其它的线程就不能操作mutex,直到这个线程将mutex解锁。

 示例1: 

#include <mutex>

std::mutex mtx;
int num = 0;

void thread_func(int& n)
{
	for (int i = 0; i < 10; ++i)
	{
		mtx.lock();
		n++;
		cout << "n: " << n << endl;;
		mtx.unlock();
	}
}

int main()
{
	std::thread myThread[10];
	for (std::thread& a : myThread)
	{
		a = std::thread(thread_func, std::ref(num));
		a.join();
	}

	std::cout << "num = " << num << std::endl;
	std::cout << "Main thread exits!" << std::endl;
	return 0;
}

2 lock_guard

std::lock_guard是C++标准库中的一个模板类,用于实现资源的自动加锁和解锁。它是基于RAII的设计理念,能够确保在作用域结束时自动释放锁资源,避免了手动管理锁的复杂性和可能出现的错误。

std::lock_guard的主要特点如下:

1 自动加锁: 在创建std::lock_guard对象时,会立即对指定的互斥量进行加锁操作。这样可以确保在进入作用域后,互斥量已经被锁定,避免了并发访问资源的竞争条件。
2 自动解锁:std::lock_guard对象在作用域结束时,会自动释放互斥量。无论作用域是通过正常的流程结束、异常抛出还是使用return语句提前返回,std::lock_guard都能保证互斥量被正确解锁,避免了资源泄漏和死锁的风险。
3 适用于局部锁定: 由于std::lock_guard是通过栈上的对象实现的,因此适用于在局部范围内锁定互斥量。当超出std::lock_guard对象的作用域时,互斥量会自动解锁,释放控制权。

#include <mutex>

std::mutex mtx;  
int num = 0;
void thread_func(int& n)
{
    std::lock_guard<std::mutex> lock(mtx);  // 加锁互斥量
    std::cout << "n: " << n++ << std::endl;
    // 执行需要加锁保护的代码
}  

int main()
{
    std::thread myThread[10];
    for (std::thread& a : myThread)
    {
    	a = std::thread(thread_func, std::ref(num));
    	a.join();
    }
    std::cout << "num == " << num << std::endl;
    return 0;
}

3 unique_lock 

std::unique_lock的主要特点如下:

  • 自动加锁和解锁: 与std::lock_guard类似,std::unique_lock在创建对象时立即对指定的互斥量进行加锁操作,确保互斥量被锁定。在对象的生命周期结束时,会自动解锁互斥量。这种自动加锁和解锁的机制避免了手动管理锁的复杂性和可能出现的错误。
  • 支持灵活的加锁和解锁: 相对于std::lock_guard的自动加锁和解锁,std::unique_lock提供了更灵活的方式。它可以在需要的时候手动加锁和解锁互斥量,允许在不同的代码块中对互斥量进行多次加锁和解锁操作。
  • 支持延迟加锁和条件变量:std::unique_lock还支持延迟加锁的功能,可以在不立即加锁的情况下创建对象,稍后根据需要进行加锁操作。此外,它还可以与条件变量(std::condition_variable)一起使用,实现更复杂的线程同步和等待机制。
#include <mutex>

std::mutex mtx;  // 互斥量

void thread_function()
{
    std::unique_lock<std::mutex> lock(mtx);  // 加锁互斥量
    std::cout << "Thread running" << std::endl;
    // 执行需要加锁保护的代码

    lock.unlock();  // 手动解锁互斥量
    // 执行不需要加锁保护的代码

    lock.lock();  // 再次加锁互斥量
    // 执行需要加锁保护的代码
}
// unique_lock对象的析构函数自动解锁互斥量

int main()
{
    std::thread t1(thread_function);
    t1.join();
    std::cout << "Main thread exits!" << std::endl;
    return 0;
}

在上述示例中,std::unique_lock对象lock会在创建时自动加锁互斥量,析构时自动解锁互斥量。我们可以通过调用lockunlock函数手动控制加锁和解锁的时机,以实现更灵活的操作。

四 condition_variable

std::condition_variable是C++标准库中的一个类,用于在多线程编程中实现线程间的条件变量和线程同步。它提供了等待通知的机制,使得线程可以等待某个条件成立时被唤醒,或者在满足某个条件时通知其他等待的线程。其提供了以下几个函数用于等待和通知线程:

 

std::condition_variable的主要特点如下:

  • 等待和通知机制:std::condition_variable允许线程进入等待状态,直到某个条件满足时才被唤醒。线程可以调用wait函数进入等待状态,并指定一个互斥量作为参数,以确保线程在等待期间互斥量被锁定。当其他线程满足条件并调用notify_one或notify_all函数时,等待的线程将被唤醒并继续执行。
  • 与互斥量配合使用:std::condition_variable需要与互斥量(std::mutex或std::unique_lock<std::mutex>)配合使用,以确保线程之间的互斥性。在等待之前,线程必须先锁定互斥量,以避免竞争条件。当条件满足时,通知其他等待的线程之前,必须再次锁定互斥量。
  • 支持超时等待:std::condition_variable提供了带有超时参数的等待函数wait_for和wait_until,允许线程在等待一段时间后自动被唤醒。这对于处理超时情况或限时等待非常有用。

示例:支持两个线程交替打印,一个打印奇数,一个打印偶数, 并且偶数优先

#include <thread>
#include <mutex>
#include <condition_variable>

int main()
{
	std::mutex mtx;
	condition_variable c;
	int n = 100;
	bool flag = true;

	thread t1([&]() {
		int i = 0;
		while (i < n)
		{
			unique_lock<mutex> lock(mtx);
			while (!flag)
				c.wait(lock);
			cout << i << endl;
			flag = false;
			i += 2; // 偶数
			c.notify_one();
		}
		});

	thread t2([&]() {
		int j = 1;
		while (j < n)
		{
			unique_lock<mutex> lock(mtx);
			while (flag)
				c.wait(lock);
			cout << j << endl;
			j += 2; // 奇数
			flag = true;
			c.notify_one();
		}
		});


	t1.join();
	t2.join();
	return 0;
}

五 std::atomic

std::mutex可以很好地解决多线程资源争抢的问题,但它每次循环都要加锁、解锁,这样固然会浪费很多的时间。

在 C++ 中,std::atomic 是用来提供原子操作的类,atomic,本意为原子,原子操作是最小的且不可并行化的操作。这就意味着即使是多线程,也要像同步进行一样同步操作原子对象,从而省去了互斥量上锁、解锁的时间消耗。

使用 std::atomic 可以保证数据在操作期间不被其他线程修改,这样就避免了数据竞争,使得程序在多线程并发访问时仍然能够正确执行。

示例:

#include<vector>
#include <atomic>

int main()
{
	atomic<int> x = 0;//不用加锁了
	auto Func = [&](int n)
	{
		for (int i = 0; i < n; i++)
		{
			x++;
		}
	};

	int n;
	cin >> n;
	vector<thread> vthds(n);
	for (auto& thd : vthds)
	{
		thd = thread(Func, 10);
	}

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

	cout << x << endl;
	return 0;
}

总结: C++ 多线程基础就是这些, 难度不大, 重在理解,  但是要注意线程安全, 合理使用. 继续加油! 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kafka 图形化工具 Eagle安装

Kafka 图形化工具 Eagle 3.0.1版本安装 1、安装JDK jdk安装 2、安装kafka 如未安装kafka&#xff0c;需要先安装完kafka 3、下载kafka-eagle 官网下载地址 wget https://github.com/smartloli/kafka-eagle-bin/archive/v3.0.1.tar.gz #移动到安装目录 mv v3.0.1.tar.gz…

vue实现echarts饼图自动轮播

echarts官网&#xff1a;Examples - Apache ECharts echartsFn.ts 把echarts函数封装成一个文件 import * as echarts from "echarts";const seriesData [{"value": 12,"name": "过流报警"},{"value": 102,"name&qu…

CSS动画案例6

目录 一、介绍二、静态页面的布局1.HTML部分2.CSS 三、动画交互四、结束语 一、介绍 本节内容我们来仿照一个网站&#xff08;戳我进网站&#xff09;的进入页面时的doing动画部分&#xff0c;首先是大标题从最小变为最大&#xff0c;然后是副标题从下向上走&#xff0c;最后是…

Proteus8.17下载安装教程

Proteus是一款嵌入式系统仿真开发软件&#xff0c;实现了从原理图设计、单片机编程、系统仿真到PCB设计&#xff0c;真正实现了从概念到产品的完整设计&#xff0c;其处理器模型支持8051、HC11、PIC10/12/16/18/24/30/DsPIC33、AVR、ARM、8086和MSP430等&#xff0c;能够帮助用…

工作-k8s问题处理篇

前言&#xff1a;公司这边为集团&#xff0c;所以项目较多&#xff0c;我目前总负责的项目架构有十六个&#xff0c;其中还有海外项目&#xff0c;不过底下也有一定的细分&#xff0c;同事解决不了的问题会升级到我这&#xff0c;只k8s容器平台常用的就有三种&#xff0c;一种是…

spring boot3.3.5 logback-spring.xml 配置

新建 resources/logback-spring.xml 控制台输出颜色有点花 可以自己更改 <?xml version"1.0" encoding"UTF-8"?> <!--关闭文件扫描 scanfalse --> <configuration debug"false" scan"false"><springProperty …

MySQL--SQL优化

目录 1 插入数据 1.1 insert优化 1.1.1 批量插入 1.1.2 手动提交事务 1.1.3 主键顺序插入 1.2 大批量插入数据 2 主键优化 2.1 数据组织方式 2.2 页分裂 2.3 页合并 2.4 主键设计原则 3 order by优化 4 group by优化 1. 使用索引 2. 减少数据集大小 3. 使用合适的聚…

【计算机网络】实验2:总线型以太网的特性

实验 2&#xff1a;总线型以太网的特性 一、 实验目的 加深对MAC地址&#xff0c;IP地址&#xff0c;ARP协议的理解。 了解总线型以太网的特性&#xff08;广播&#xff0c;竞争总线&#xff0c;冲突&#xff09;。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实…

Java函数式编程【二】【Stream的装饰】【中间操作】【map映射器】【摊平映射器flatMap】

一、Java的Stream流式编程中的中间操作 Java的Stream流式编程中&#xff0c;中间操作是对数据流进行处理的一种方式&#xff0c;这些操作通常返回流对象本身&#xff0c;以便可以链接更多的操作。以下是一些常见的中间操作&#xff1a; filter(Predicate predicate) - 用于通过…

使用pymupdf提取PDF文档中的文字和其颜色

最近我在捣鼓一个PDF文件&#xff0c;想把它里面的文字和文字颜色给提取出来。后来发现有个叫pymupdf的库能搞定这事儿。操作起来挺简单的&#xff0c;pymupdf的示例文档里就有现成的代码可以参考。 how-to-extract-text-with-color 我本地的测试代码如下&#xff1a; impor…

MYSQL 多表练习

Sutdent表的定义 ---------------------------------------------------------------------------------------------------- | 字段名 | Id | Name | Sex | Birth | Department | Address | -------------------…

2024年12月HarmonyOS应用开发者基础认证全新题库

注意事项&#xff1a;切记在考试之外的设备上打开题库进行搜索&#xff0c;防止切屏三次考试自动结束&#xff0c;题目是乱序&#xff0c;每次考试&#xff0c;选项的顺序都不同 更新时间&#xff1a;2024年12月3日 这是基础认证题库&#xff0c;不是高级认证题库注意看清楚标…

一文解析Kettle开源ETL工具!

ETL&#xff08;Extract, Transform, Load&#xff09;工具是用于数据抽取、转换和加载的软件工具&#xff0c;用于支持数据仓库和数据集成过程。Kettle作为传统的ETL工具备受用户推崇。本文就来详细说下Kettle。 一、Kettle是什么&#xff1f; Kettle 是一款开源的 ETL&#x…

【模型剪枝】YOLOv8 模型剪枝实战 | 稀疏化-剪枝-微调

文章目录 0. 前言1. 模型剪枝概念2. 模型剪枝实操2.1 稀疏化训练2.2 模型剪枝2.3 模型微调总结0. 前言 无奈之下,我还是写了【模型剪枝】教程🤦‍♂️。回想当年,在写《YOLOv5/v7进阶实战专栏》 时,我经历了许多挫折,才最终完成了【模型剪枝】和【模型蒸馏】的内容。当时…

【算法刷题指南】优先级队列

&#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系列 &#x1f308;Linux学习专栏&#xff1a; 南桥谈Linux &#x1f308;数据结构学习专栏&#xff1a; 数据结构杂谈 &#x1f308;数据…

大语言模型微调与 XTuner 微调实战

1 大语言模型微调 1.1 什么是微调 大语言模型微调&#xff08;Fine-tuning of Large Language Models&#xff09;是指在预训练的大型语言模型基础上&#xff0c;使用特定任务的数据进一步训练模型&#xff0c;以使其更好地适应和执行特定任务的过程&#xff0c;用于使LLM&am…

C#学写了一个程序记录日志的方法(Log类)

1.错误和警告信息单独生产文本进行记录&#xff1b; 2.日志到一定内存阈值可以打包压缩&#xff0c;单独存储起来&#xff0c;修改字段MaxLogFileSizeForCompress的值即可&#xff1b; 3.Log类调用举例&#xff1a;Log.Txt(JB.信息,“日志记录内容”,"通道1"); usi…

【前端】特殊案例分析深入理解 JavaScript 中的词法作用域

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 &#x1f4af;前言&#x1f4af;案例代码&#x1f4af;词法作用域&#xff08;Lexical Scope&#xff09;与静态作用域什么是词法作用域&#xff1f;代码执行的详细分析 &#x1f4af;函数定义与调用的…

【Docker】Docker配置远程访问

配置Docker的远程访问&#xff0c;你需要按照以下步骤进行操作&#xff1a; 1. 在Docker宿主机上配置Docker守护进程监听TCP端口 Docker守护进程默认只监听UNIX套接字&#xff0c;要实现远程访问&#xff0c;需要修改配置以监听TCP端口。 ‌方法一&#xff1a;修改Docker服务…

贪心算法题

0简介 0.1什么是贪心算法 贪心算法是用贪婪(鼠目寸光)的角度&#xff0c;找到解决问题的最优解 贪心策略&#xff1a;(从局部最优 --> 整体最优) 1把解决问题的过程分为若干步&#xff1b; 2解决每一个问题时&#xff0c;都选择当前“看上去”最优的解法&#xff1b; 3“…