首页 > 其他分享 >异构计算关键技术之多线程技术(三)

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

时间:2024-02-01 14:32:15浏览次数:22  
标签:include 关键技术 异构计算 producer produce 线程 多线程 consumer


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


一、多线程概述

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;
}

异构计算关键技术之多线程技术(三)_线程安全_02

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;
}

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

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;
}

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

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

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

3. 并发网络编程

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

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

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


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

1. 死锁与饥饿

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

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

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

2. 竞态条件和原子操作

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

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

3. 线程安全性

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


六、未完待续

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

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

欢迎+V:beijing_bubei

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

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


七、参考文献

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

标签:include,关键技术,异构计算,producer,produce,线程,多线程,consumer
From: https://blog.51cto.com/u_16419576/9527098

相关文章

  • 浏览器支持多线程下载,IDM还是地表最强吗?
    引言平时大家下载小文件一般会用浏览器自带的下载,而大文件却要搭配下载器(比如有亿点点贵的IDM),但是大家知道吗,我们的浏览器自带多线程下载,只是默认是禁用的,试了试真的还不错!启动新功能这里以新版MicrosoftEdge为例,打开edge://flags/#enable-parallel-downloading(Chrome为c......
  • 深入浅出Java多线程(四):线程状态
    引言大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第四篇内容:线程状态。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!在现代软件开发中,多线程编程已经成为提升应用程序性能和响应能力的关键技术。Java作为一门支持多线程编程的主流语言,其内置的丰富并......
  • 一次因PageHelper引起的多线程复用问题的排查和解决 | 京东物流技术团队
    A、ProblemDescription1\.PageHelper方法使用了静态的ThreadLocal参数,在startPage()调用紧跟MyBatis查询方法后,才会自动清除ThreadLocal存储的对象。2\.当一个线程先执行了A方法的PageHelper.startPage(intpageNum,intpageSize)后,在未执行到SQL语句前,因为代码抛异常而提前结束......
  • 多线程之读者写者模型(三千字长文详解)
    多线程之读者写者模型什么是读者写者问题?为了能理解这个概念我们先举个列子:我们在小时候,通常有一个东西叫做——黑板报!在一个班级上,有一个叫小明的学生,他字写的很高,有一天他正在画黑板报,同学们在他旁边看,窃窃私语的猜他在画什么东西!有的猜说画的是蛇,有的说画的是龙,等等但是到......
  • 一次因PageHelper引起的多线程复用问题的排查和解决 | 京东物流技术团队
    A、ProblemDescription1.PageHelper方法使用了静态的ThreadLocal参数,在startPage()调用紧跟MyBatis查询方法后,才会自动清除ThreadLocal存储的对象。2.当一个线程先执行了A方法的PageHelper.startPage(intpageNum,intpageSize)后,在未执行到SQL语句前,因为代码抛异常而提前结束......
  • 深入浅出Java多线程(三):线程与线程组
    「引言」大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第三篇内容:线程与线程组。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!在现代软件开发中,多线程编程已成为提升程序性能和并发能力的关键技术之一。Java作为主流的面向对象编程语言,其对多线程的支......
  • java用多线程批次查询大量数据(Callable返回数据)方式
    我看到有的数据库是一万条数据和八万条数据还有十几万条,几百万的数据,然后我就想拿这些数据测试一下,发现如果用java和数据库查询就连一万多条的数据查询出来就要10s左右,感觉太慢了。然后网上都说各种加索引,加索引貌似是有查询条件时在某个字段加索引比较快一些,但是毕竟是人家的库不......
  • 深入浅出Java多线程(二):Java多线程类和接口
    引言大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第二篇内容:Java多线程类和接口。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!在现代计算机系统中,多线程技术是提升程序性能、优化资源利用和实现并发处理的重要手段。特别是在Java编程语言中,多线程机......
  • 多线程
    多线程理论(1)什么是线程在Python中,线程(Thread)是执行单元的最小单位。线程是进程内的一条执行路径,每个线程都有自己的执行序列、执行环境和栈空间,但它们共享同一个进程的地址空间。在多线程编程中,可以同时运行多个线程,每个线程执行不同的任务,从而实现并发执行。相比于多进......
  • C++多线程 第一章 你好,C++并发世界
    第一章你好,C++并发世界C++并发并发(concurrency):主要包括任务切换与硬件并发两类.并发(concurrency)实际上与多线程(multithreading)存在差异.并发的种类任务切换(taskswitching):计算机在某一时刻只可以真正执行一个任务,但它可以每秒切换任务许多次.通过做一......