异构计算关键技术之多线程技术(三)

news2025/2/2 22:38:50

异构计算关键技术之多线程技术(三)

一、多线程概述

1. 多线程的概念与优劣

多线程是指在程序中同时运行多个线程,每个线程都可以独立执行不同的代码段,且各个线程之间共享程序的数据空间和资源。

优劣

优点:提高程序的处理能力,增加相应速度和交互性。

缺点:线程的切换有一定的开销,且多线程容易引发数据竞争和死锁等问题。

2. 多进程的应用场景

多线程常用于需要同时完成多个任务或者执行多个耗时操作的应用场景,如并发服务器、GUI程序、游戏开发等。

3. 多线程的基本原理

多线程的核心就是将程序分为多个线程并发执行,其中每个线程都独立运行,但共享同一组全局变量和操作系统资源,由于资源共享,使用线程时需要保证对资源的安全访问。

二、C++多线程编程基础

1. 多线程库的选择

C++常用的多线程库有windows API、posix threads和c++ 11标准库,根据编译环境和目标系统选择不同的库。

2. 线程的创建

以下是一个简单的使用C++11标准库创建线程的例子:

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

// 线程函数
void hello(int num)
{
    cout << "hello, concurrent world! there are " << num << " threads. " <<endl;
}

//main()主线程
int main()
{
    int num = thread::hardware_concurrency();// 获取并发线程数, 可以是cpu核心的数量
    thread t(hello, num); //创建线程,并传递参数
    t.join(); //等待线程结束
    return 0;
}

在这里插入图片描述

3. 线程的同步与互斥

多个线程同时访问共享资源时,会出现数据竞争的问题。为了保证资源安全访问,需要进行同步与互斥

以下是一个简单的使用C++11标准库进行同步和互斥的例子:

#include <iostream>
#include <thread>
#include <mutex> //互斥量头文件
#include <atomic>
using namespace std;

mutex mtx;//互斥量对象

// 线程函数
void hello(int num)
{   
    mtx.lock(); //加锁
    cout << "hello, concurrent world! there are " << num << " threads. " <<endl;
    mtx.unlock(); //解锁
}

//main()主线程
int main()
{
    int num = thread::hardware_concurrency();// 获取并发线程数, 可以是cpu核心的数量
    thread t1(hello, num); //创建线程,并传递参数
    thread t2(hello, num); //创建线程,并传递参数
    t1.join(); //等待线程结束
    t2.join(); //等待线程结束
    return 0;
}

在这里插入图片描述

4. 线程的销毁

线程的销毁可以通过join()或detach()方法实现。

其中join()方法会阻塞调用线程直到被调用的线程执行完毕,而detach()方法则会将调用线程和被调用的线程分离,使两个线程可以独立运行。

5. 线程安全性问题

多线程编程常见的线程安全性问题有数据竞争、死锁、优先级反转等,需要使用锁、条件变量、原子变量等工具进行保护,以保证程序的正确性和高效性。

三、C++多线程编程高级特性

1. 线程池

线程池是一组预先创建好的线程资源,它们可以被多个任务共享使用,而不必每次都创建线程,从而减少线程创建、销毁、切换的时间,从而提高程序的效率。

2. 原子变量

原子变量是一种特殊类型的变量,支持原子操作,这些操作能保证在多线程环境下的可靠性和一致性。

以下是一个简单的使用C++11标准库进行原子操作的例子:

#include <iostream>
#include <thread>
#include <mutex> //互斥量头文件
#include <atomic> //原子变量头文件
using namespace std;

//mutex mtx;//互斥量对象

atomic<int> cnt(0);


// 线程函数
void increase()
{   
    for (int i = 0; i < 100; i++) {
        cnt++; //原子操作
    }
    
}

//main()主线程
int main()
{
    thread t1(increase);
    thread t2(increase); //创建2个线程
    t1.join(); //等待线程结束
    t2.join(); //等待线程结束
    cout << "cnt = " << cnt << endl; //输出结果
    return 0;
}

在这里插入图片描述

3. 无锁数据结构

无锁数据结构是一种常见的高并发数据结构,它可以避免线程之间的互斥和等待,从而提高程序的并发性能

4. 多线程的性能调优

多线程程序的性能调优可以从多个角度入手,如线程数的优化任务切分负载均衡等方面。同时还可以使用一些性能分析工具和调试工具来进行监测和调试,以保证程序的正确性和高效性。

四、多线程编程实践案例

1. 生产者-消费者模型

生产者-消费者模型是一种经典的多线程模型,用于解决线程同步和数据共享的问题。生产者线程负责产生数据,消费者线程负责消费数据,并且两者都需要共享同样的数据缓冲区。

这个模型的实现可以采用多种方式,例如使用信号量、条件变量和互斥量等同步机制来实现。

下面是一个C++实现的生产者-消费者模型的示例代码,其中假设数据缓冲区的大小为10:

#include <iostream>
#include <thread>
#include <mutex> //互斥量头文件
#include <atomic> //原子变量头文件
#include <condition_variable>
#include <queue>

using namespace std;

mutex mtx;//互斥量对象, 用于保护共享数据的访问
condition_variable cond; //条件变量,用于线程间同步
std::queue<int> buffer; //数据缓冲区


const int MAX_SIZE = 10; // 缓冲区大小

// 生产者线程函数
void producer()
{
    for (int i = 0; i < 20; i++) { //生产20个数据
        unique_lock<mutex> lock(mtx); //加锁
        // 如果缓冲区满了,等待消费者消费
        cond.wait(lock, []() {return buffer.size() < MAX_SIZE; });

        buffer.push(i); //生产者向缓冲区中添加数据
        cout << "producer: produce " << i <<endl;
        cond.notify_one(); //通知一个等待的消费者线程
    }

}

// 消费者线程函数
void consumer()
{
    int data = 0;
    while(data != 19) // 消费者消费20个数据
    {
        unique_lock<mutex> lock(mtx); //加锁
        // 如果缓冲区为空,等待生产者生产
        cond.wait(lock, []() {return buffer.size() > 0;});

        data = buffer.front(); //消费者从缓冲区中取出数据
        buffer.pop();
        cout << "consumer: consume " <<data <<endl;
        cond.notify_one(); //通知一个等待的生产者线程
    }
}

//main()主线程
int main()
{
    thread t1(producer);
    thread t2(consumer); //创建2个线程
    t1.join(); //等待线程结束
    t2.join(); //等待线程结束

    return 0;
}

producer: produce 0
producer: produce 1
producer: produce 2
producer: produce 3
producer: produce 4
producer: produce 5
producer: produce 6
producer: produce 7
producer: produce 8
producer: produce 9
consumer: consume 0
consumer: consume 1
producer: produce 10
producer: produce 11
consumer: consume 2
consumer: consume 3
consumer: consume 4
consumer: consume 5
consumer: consume 6
consumer: consume 7
consumer: consume 8
consumer: consume 9
consumer: consume 10
consumer: consume 11
producer: produce 12
producer: produce 13
producer: produce 14
producer: produce 15
producer: produce 16
producer: produce 17
producer: produce 18
producer: produce 19
consumer: consume 12
consumer: consume 13
consumer: consume 14
consumer: consume 15
consumer: consume 16
consumer: consume 17
consumer: consume 18
consumer: consume 19

这个程序演示了一个基本的生产者-消费者模型,其中使用了互斥量和条件变量来保证线程间同步,并使用队列作为共享数据缓冲区。

在生产者线程中,如果缓冲区已满,则等待消费者线程消费。

在消费者线程中,如果缓冲区已空则等待生产者线程生产。

注意:使用条件变量时需要加上互斥量,以确保条件的正确性。

2. 多线程数据分析

多线程数据分析是在多线程环境下对大量数据进行处理的一种常见应用。

对于需要处理大量数据的应用,使用多线程可以有效提高程序的运行效率。例如,在数据挖掘、机器学习、图像处理和模拟等领域,线程并行化已成为一种常用的技术手段。

下面是一个简化的C++实现的多线程数据分析示例代码,其中假设需要处理10万个数:

#include <iostream>
#include <thread>
#include <mutex> //互斥量头文件
#include <atomic> //原子变量头文件
#include <condition_variable>
#include <queue>
#include <vector>

using namespace std;

const int MAX_NUM = 100000; //待处理的数据个数
const int THREAD_NUM = 4; //线程数量

int nums[MAX_NUM];// 数据数组
int result[THREAD_NUM] = {0}; //处理数据结果

mutex mtx;//互斥量对象, 用于保护共享数据的访问

// 生产者线程函数
void worker(int id)
{
    int start = id * (MAX_NUM / THREAD_NUM); //计算该线程处理的数据区间
    int end = (id + 1) * (MAX_NUM / THREAD_NUM);

    int sum = 0;
    for (int i = start; i < end; i++) { //处理数据
        sum += nums[i];
    }

    {
        unique_lock<mutex> lock(mtx);// 加锁
        result[id] = sum; //更新处理结果
    }
}


//main()主线程
int main()
{
    for (int i = 0; i < MAX_NUM; i++) {
        nums[i] = i % 100;
    }

    vector<thread> threads;// 存储线程对象

    for (int i = 0; i < THREAD_NUM; i++) {
        threads.emplace_back(worker, i); //创建线程并加入到线程向量
    }

    for (auto& thread : threads) {
        thread.join(); //等待线程结束
    }

    int final_sum = 0;
    for (int i = 0; i < THREAD_NUM; i++) {
        final_sum += result[i]; //汇总处理结果
    }

    cout << "final sum is " << final_sum <<endl;
    
    return 0;
}

在这里插入图片描述

这个程序演示了一个基本的多线程数据分析过程,其中使用了4个线程来并行处理10万个数据。在每个线程中,通过指定数据分块的方式,处理部分数据,并累加处理结果。最后主线程将每个线程的处理结果进行汇总,得到最终的处理结果。

注意:在使用多线程时需要注意对共享数据的访问控制,例如使用互斥量来保证数据的正确性。

3. 并发网络编程

并发网络编程是将多线程和网络编程技术结合起来,用于构建高并发网络应用程序的一种技术手段。

在网络编程中,需要处理大量的来自不同客户端的连接请求,并且需要同时处理多个客户端之间的数据交换。

通过使用多线程技术来并发处理不同客户端的请求和数据交换,可以提高网络应用程序的性能和可扩展性。

五、C++多线程编程的常见问题与应对策略

1. 死锁与饥饿

死锁和饥饿是多线程编程中常见的问题,需要特殊注意。死锁是指两个或多个线程互相等待对方释放锁的情况,导致线程无法继续执行的问题。饥饿则是指某个线程无法获得所需资源,导致该线程无法继续执行的问题。

对于死锁问题一种常见的解决方式是避免使用多个锁或在使用多个锁时统一获取锁的顺序,以避免出现环路依赖死锁的情况。另一种常见的解决方式是使用RAII技术,将锁的获取和释放放在同一个类中,使用智能指针管理这些类,避免手动操作锁的获取和释放,减少人为错误。

对于饥饿问题需要让所有线程公平竞争资源,避免一些线程独占资源导致其他线程无法继续执行。一种常见的解决方式是使用队列等数据结构,在多个线程之间共享数据资源,让所有线程均有机会获得资源,从而避免饥饿问题的发生。

2. 竞态条件和原子操作

竞态条件是指多个线程同时访问和修改同一个共享资源时,导致最终结果依赖于不同线程执行顺序的情况。原子操作则是指不可被中断的操作,可以保证对一个共享变量的操作是不可分割、完整的。

对于竞态条件问题一种常见的解决方式是使用锁和互斥量等同步机制来控制共享资源的访问和修改,保证同一时间只有一个线程可以访问和修改共享资源。另一种常见的解决方式是使用原子操作,通过CAS(Compare-and-Swap)等机制保证对共享变量的操作是原子性的,从而避免竞态条件的发生。

3. 线程安全性

多线程编程中线程安全性是一个非常重要的问题,指的是在多个线程并发执行时,程序的行为仍然是正确的。对于线程安全性的保证,可以采用许多不同的技术手段,例如使用互斥量、条件变量、原子操作、Thread-Local Storage等技术,避免共享资源的访问冲突和数据竞争,从而保证线程安全性。

六、未完待续

下章将继续介绍核心的基本概念:内核态的线程/进程技术。

欢迎关注知乎:北京不北,+vbeijing_bubei

欢迎+V:beijing_bubei

欢迎关注douyin:near.X (北京不北)

获得免费答疑,长期技术交流。

七、参考文献

全性

多线程编程中线程安全性是一个非常重要的问题,指的是在多个线程并发执行时,程序的行为仍然是正确的。对于线程安全性的保证,可以采用许多不同的技术手段,例如使用互斥量、条件变量、原子操作、Thread-Local Storage等技术,避免共享资源的访问冲突和数据竞争,从而保证线程安全性。

六、未完待续

下章将继续介绍核心的基本概念:内核态的线程/进程技术。

欢迎关注知乎:北京不北,+vbeijing_bubei

欢迎+V:beijing_bubei

欢迎关注douyin:near.X (北京不北)

获得免费答疑,长期技术交流。

七、参考文献

https://zhuanlan.zhihu.com/p/680367597

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

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

相关文章

Leetcode29-最大频率元素计数(3005)

1、题目 给你一个由 正整数 组成的数组 nums 。 返回数组 nums 中所有具有 最大 频率的元素的 总频率 。 元素的 频率 是指该元素在数组中出现的次数。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,2,3,1,4] 输出&#xff1a;4 解释&#xff1a;元素 1 和 2 的频率为 …

模板讲解之进阶

在之前的C入门的博客中我们就学习到了模板初阶&#xff0c;今天我们来学习模板的进阶&#xff0c;以便于更好地将模板运用到代码中 非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的…

使用Docker部署DashDot服务器仪表盘并结合cpolar实现公网监测服务器

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 文章目录 1. 本地环境检查1.1 安装docker1.2 下载Dashdot镜像 2.…

氟化硼二吡咯甲基四嗪,BDP-FL-methyltetrazine,能够提高检测的灵敏度和特异性

BDP FL methyltetrazine&#xff0c;氟化硼二吡咯甲基四嗪&#xff0c;BDP-FL-methyltetrazine&#xff0c;能够提高检测的灵敏度和特异性 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;BDP FL methyltetrazine&#xff0c;氟化硼二吡咯甲基四嗪&#xff0c;BDP-…

Golang 并发控制方式有哪些

Go语言中的goroutine是一种轻量级的线程&#xff0c;其优点在于占用资源少、切换成本低&#xff0c;能够高效地实现并发操作。但如何对这些并发的goroutine进行控制呢&#xff1f; 一提到并发控制&#xff0c;大家最先想到到的是锁。Go中同样提供了锁的相关机制&#xff0c;包…

Android.bp入门指南之浅析Android.bp文件

文章目录 Android.bp文件是什么&#xff1f;Android.bp的主要作用模块定义依赖关系构建规则模块属性插件支持模块的可配置性 为什么会引入Android.bp语法例子 Android.bp文件是什么&#xff1f; Android.bp 文件是 Android 构建系统&#xff08;Android Build System&#xff…

第1章 简单使用 Linux

第1章 简单使用 Linux 1.1 Linux 的组成 1.2 远程连接 首先以 root 用户登录到 Linux 系统&#xff0c;然后在 Terminal 终端上输入 ip add 命令&#xff0c;来查看 IP 地址。 上图中的 192.168.72.128 就是 IP 地址。 然后打开 XShell 远程连接工具。 然后在命令提示符下输…

2024年美赛数学建模A题思路分析 - 资源可用性和性别比例

# 1 赛题 问题A&#xff1a;资源可用性和性别比例 虽然一些动物物种存在于通常的雄性或雌性性别之外&#xff0c;但大多数物种实质上是雄性或雌性。虽然许多物种在出生时的性别比例为1&#xff1a;1&#xff0c;但其他物种的性别比例并不均匀。这被称为适应性性别比例的变化。…

计算机设计大赛 深度学习 python opencv 火焰检测识别 火灾检测

文章目录 0 前言1 基于YOLO的火焰检测与识别2 课题背景3 卷积神经网络3.1 卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV54.1 网络架构图4.2 输入端4.3 基准网络4.4 Neck网络4.5 Head输出层 5 数据集准备5.1 数…

EasyX图形库学习(一)

目录 一、easyX图形库基本介绍 1、easyX的原理 2、easyX的安装 3、easyX的颜色&#xff08;RGB颜色模型&#xff09; 颜色模型相关函数: 4、easyX的坐标 二、相关函数介绍: 绘图设备相关函数&#xff1a; 图形颜色及样式设置相关函数: 图形绘制相关函数: 文字输出相关…

VSCode开发常用扩展记录

1、Chinese 2、document this 可以自动为ts和js文件生成jsDoc注释 3、ESLint 能够查找并修复js代码中的问题 4、koroFileHeader 5、Prettier 代码格式化

MATLAB Fundamentals>>>Centering and Scaling

MATLAB Fundamentals>Common Data Analysis Techniques>Polynomial Fitting>Centering and Scaling 数据导入 This code sets up the activity. yr 2000:2007 penguins [5.49 7.03 7.73 7.70 9.29 9.21 11.89 10.85] 附加练习 How does the model look?…

1451A/D/F捷变信号发生器

01 1451A/D/F捷变信号发生器 产品综述&#xff1a; 1451系列捷变信号发生器采用直接数字合成&#xff08;DDS&#xff09;技术和直接模拟合成技术&#xff08;ADS&#xff09;相结合的设计方案&#xff0c;实现覆盖10MHz~3/20/40GHz全频段的频率捷变&#xff0c;捷变时间小于…

智能预测:数字化时代的服装企业如何实现准确的需求规划

在数字化时代&#xff0c;智能预测成为服装企业实现准确需求规划的关键。通过充分利用先进的技术手段&#xff0c;企业能够更精准地洞察市场动态、了解消费者行为&#xff0c;从而使生产计划更加敏捷、高效。以下是数字化时代服装企业实现准确需求规划的关键步骤和策略。 1.数…

华为云CodeArts Snap荣获信通院优秀大模型案例及两项荣誉证书

2024年1月25日&#xff0c;中国人工智能产业发展联盟智能化软件工程工作组&#xff08;AI for Software Engineering&#xff0c;下文简称AI4SE&#xff09;在京召开首届“AI4SE创新巡航”活动。在活动上&#xff0c;华为云大模型辅助系统测试代码生成荣获“2023AI4SE银弹优秀案…

python中的可变与不可变、深拷贝和浅拷贝

个人猜想&#xff08;很遗憾失败了&#xff09; 在硬盘或者系统中存在一个字符集 如果存在硬盘中&#xff0c;那么硬盘出厂的时候他的字符集所占用的空间就已经确定了。 如果存在于系统的话&#xff0c;硬盘应该在出厂的时候为系统设置一个存储系统字符集的地方。在安装系统…

[蓝桥杯难题总结-Python]乘积最大

乘积最大 题目描述 今年是国际数学联盟确定的“ 2000 ——世界数学年”&#xff0c;又恰逢我国著名数学家华罗庚先生诞辰 90 周年。在华罗庚先生的家乡江苏金坛&#xff0c;组织了一场别开生面的数学智力竞赛的活动&#xff0c;你的一个好朋友 XZ 也有幸得以参加。活动中&…

部署实战--修改jar中的文件并重新打包成jar文件

一.jar文件 JAR 文件就是 Java Archive &#xff08; Java 档案文件&#xff09;&#xff0c;它是 Java 的一种文档格式JAR 文件与 ZIP 文件唯一的区别就是在 JAR 文件的内容中&#xff0c;多出了一个META-INF/MANIFEST.MF 文件META-INF/MANIFEST.MF 文件在生成 JAR 文件的时候…

【jenkins】主从机制及添加Slave节点操作

一、master-slave 日常构建Jenkins任务中&#xff0c;会经常出现下面的情况&#xff1a; 自动化测试需要消耗大量的 CPU 和内存资源&#xff0c;如果服务器上还有其他的服务&#xff0c;可能会造成卡顿或者宕机这样的情况&#xff1b; Jenkins 平台上除了这个项目&#xff0c…

SpringBoot security 安全认证(一)——登录验证

本节内容&#xff1a;使用springboot自动security模块实现用户登录验证功能&#xff1b; 登录过程如下图&#xff1a; AuthenticationManager内容实现用户账号密码验证&#xff0c;还可以对用户状态&#xff08;启用/禁用&#xff09;&#xff0c;逻辑删除&#xff0c;账号是否…