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

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

最近遇到了一个项目,需要写一个用户态的测试程序(独立进程),用来测试FPGA PCIe DMA的性能,具体的要求如下:

markdown 复制代码
1. 需要一个主线程,用来配置FPGA的寄存器,同时启动从线程;

2. 如果不进行人为干涉,子线程一直进行FPGA的相关操作,比如下发trigger信号、配置burst次数、数据长度;

3. 进行人为干涉,子线程退出,并返回子线程执行的一些信息,提供给主线程做统计和计算信息;

下面我们直接给出相关的核心代码,结合线程的理论进行分析:

c 复制代码
...
...

static int run = 0;
static int round = 0;

...

typedef struct _param
{
    struct util_mem *util;
    int burst;
    int len;
}param;

void *
recv_perf(void *data)
{
    ...
    
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(14, &mask);
    sched_setaffinity(0, sizeof(cpu_set_t), &mask);
    
    ...

    /* len and burst*/
    reg_write(..., addr, (p->len&0x0000ffff)|((p->burst&0x0000ffff)<<16)));

    ...

    while(run) {
        /* trigger */
        reg_write(..., addr, &rdata);
        while(times < p->burst) {
            data_size = recv(...,...,...);
            if (data_size == xxx) {
                
                ...
            times++;
            }
        }
        times = 0;
        cnt++;
    }
    round = cnt * p->burst;
    pthread_exit(0);
}

int main(int argc, char **argv)
{
    ...
    pthread_t tid;

    ret = start(...);

    ret = pthread_create(&tid, NULL, recv_perf, &data);
    if (ret < 0) {

    }
    else {

    }

    pthread_detach(tid);
    while(1) {
        ch = getchar();
        if (ch = 's') {
            run = 0;
            ...
            break;
        }
    }
    
    ...

    avg = (float)recv_total/round;
    
    ...
}

一、代码设计分析

这段代码非常实用,整体思想如下:

  • 主线程main函数,首先做了FPGA系统的一些初始化功能,然后起了一个从线程recv_perf();

  • 从线程主要是根据传递的参数发送给FPGA,让FPGA一直做DMA操作;

  • 主从线程分离detach();

  • 主线程while(1)循环,用来控制从线程的结束,同时通过全局变量进行传递参数;

  • 最后计算FPGA的统计信息;


二、C++多线程编程知识点归纳

1. 主线程和子线程的区别

我们先看看线程是如何创建起来的:

复制代码
进程仅仅是一个容器,包含了线程运行中所需要的数据结构等信息。

一个进程创建时,操作系统会创建一个线程,这就是主线程。

而其他的从线程,却要主线程的代码来创建,也就是由程序员来创建。

主线程

arduino 复制代码
main()函数均视为主线程,除了"不包含在thread里面的程序",均视为主线程;

子线程

arduino 复制代码
包含在thread = new thread()里面均视为子线程;

main函数

css 复制代码
main()函数作为入口开始运行,是一个进程,同时也是一个线程。在现在的操作系统中,都是多线程的。

2. 线程的创建与参数传递

这个实例中,我们需要做一个子线程,用来一直执行FPGA的操作,同时我们需要传递FPGA的配置参数,下发给FPGA寄存器空间。

linux下的多线程程序,需要使用pthread.h,链接时需要使用libthread.a。

线程的创建需要通过pthread_create来完成,声明如下:

c 复制代码
#include <pthread.h>

int pthread_create(pthread_t *thread, pthread_attr_t *attr, void* (start_routine)(void*), void *arg);
  • thread:是一个指针,线程创建成功时,用以返回创建的线程ID;
  • attr:线程属性,NULL表示使用默认;
  • start_rountine:函数指针,指被创建的线程函数;
  • arg:该参数指向传递给线程函数的参数;

实例中,接收函数recv_perf(),同时传递的参数结构体data;

3. 线程的退出

多线程中,终止执行的方式有3种,分别是:

kotlin 复制代码
1. 线程执行完成后,自行终止;
2. 线程执行种,遇到了pthread_exit()或者return;
3. 线程在执行过程种,接收到了其他线程发送的"终止执行"的信息,然后终止执行;

第一种很容易理解,不做讨论。

pthread_exit()和return

:

return

kotlin 复制代码
return 关键字用于终止函数执行,必要时还能将函数的执行结果反馈给调用者。
return 关键字不仅可以用于普通函数,线程函数中也可以使用它。

pthread_exit()

scss 复制代码
<pthread.h>头文件中,提供有一个和 return 关键字相同功能的 pthread_exit() 函数。
和之前不同,pthread_exit() 函数只适用于线程函数,而不能用于普通函数。
c 复制代码
void pthread_exit(void*retval);

retval是void*类型的指针,可以指向任何类型的数据,它指向的数据作为线程退出的返回值。

pthread_exit()和return()的区别

  • return:不仅会终止主线程执行,还会终止其他子线程的执行;
  • pthread_exit():只会终止当前线程,不会影响到其他线程的执行;

实际场景中,想要终止某个子线程,强烈建议使用pthread_exit()函数。

pthread_cancel

:

一个线程还可以向另一个线程发送"终止执行"的信号(后续称为"cancel"信号),这时候需要调用pthread_cancel()函数。

c 复制代码
int pthread_cancel(pthread_t thread);

参数thread用于接收cancel信号的目标线程。

对于接收cancel信号后,结束执行的目标线程,等同于该线程自己执行如下语句:

c 复制代码
pthread_exit(PTHREAD_CANCELED);

也就是说,当一个线程被强制终止时,它会返回pthread_cancel这个宏。

然后对于我们这个设计,巧妙的使用了run这个全局变量,用来控制子线程执行,同时利用全局变量来进行计算,是个很好的策略。

这是因为子线程在detach()以后,就无法再返回子线程的资源,会出现core。

4. detach()

detach()的作用是将子线程和主线程的关联分离,也就是说detach()后子线程在后台独立继续执行,主线程无法再获得子线程的控制权。

即使主线程结束,子线程未执行也不会结束。当主线程结束时,由运行时库负责清理和子线程相关的资源。

detach()同时也带来了一些问题,如子线程要访问主线程的对象,而主线中的对象又因为主线程结束而被销毁,导致程序崩溃。

5. 把进程/线程绑定到特定的cpu核上运行

某个进程需要较高的运行效率时,就有必要考虑将其绑定到单独的核上运行,以减小由于在不同的核上调度造成的开销。

把某个进程/线程绑定到特定的cpu核上后,该进程就会一直在此核上运行,不会再被操作系统调度到其他核上。但绑定的这个核还是可能会被调度运行其他应用程序的。(可以做隔离)

查看绑定情况

shell 复制代码
taskset -p pid

显示的是十进制,需要转换成2进制,每个1对应一个cpu(cpu从0开始)

启动时绑定

shell 复制代码
taskset -c xxx,yyy ./pcie_perf&

启动应用程序的时候绑定。

启动后绑定

shell 复制代码
taskset -cp 1,2,5,11 9865  将进程9864绑定到#1、#2、#5、#11号核上面。

taskset -cp 1,2,5-11 9865  将进程9864绑定到#1、#2、#5~#11号核上面。

代码绑定

c 复制代码
...
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(14, &mask);
sched_setaffinity(0, sizeof(cpu_set_t), &mask);
...

三、未完待续

复制代码
欢迎关注知乎:北京不北,+vbeijing_bubei

欢迎+V:beijing_bubei

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

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

四、参考文献

blog.csdn.net/qq_41854911...

相关推荐
mazhimazhi1 分钟前
GC垃圾收集时,居然还有用户线程在奔跑
后端·面试
Python私教2 分钟前
基于 Requests 与 Ollama 的本地大模型交互全栈实践指南
后端
ypf52084 分钟前
Tortoise_orm与Aerich 迁移
后端
Victor3564 分钟前
Dubbo(78)Dubbo的集群容错机制是如何实现的?
后端
lizhongxuan5 分钟前
PgBackRest备份原理详解
后端
真是他5 分钟前
多继承出现的菱形继承问题
后端
Java技术小馆6 分钟前
SpringBoot中暗藏的设计模式
java·面试·架构
李菠菜9 分钟前
POST请求的三种编码及SpringBoot处理详解
spring boot·后端
李菠菜10 分钟前
浅谈Maven依赖传递中的optional和provided
后端·maven
lqstyle14 分钟前
Redis的Set:你以为我是青铜?其实我是百变星君!
后端·面试