美文网首页
异构计算关键技术之多线程技术(四)

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

作者: 北京不北 | 来源:发表于2024-03-13 15:39 被阅读0次

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

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

    1. 需要一个主线程,用来配置FPGA的寄存器,同时启动从线程;
    
    2. 如果不进行人为干涉,子线程一直进行FPGA的相关操作,比如下发trigger信号、配置burst次数、数据长度;
    
    3. 进行人为干涉,子线程退出,并返回子线程执行的一些信息,提供给主线程做统计和计算信息;
    

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

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

    <font color=B871F78><h2>一、代码设计分析 </font></h2>

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

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

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

    • 主从线程分离detach();

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

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


    1710239945977.png

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

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

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

    进程仅仅是一个容器,包含了线程运行中所需要的数据结构等信息。
    
    一个进程创建时,操作系统会创建一个线程,这就是主线程。
    
    而其他的从线程,却要主线程的代码来创建,也就是由程序员来创建。
    

    主线程

    main()函数均视为主线程,除了“不包含在thread里面的程序”,均视为主线程;
    

    子线程

    包含在thread = new thread()里面均视为子线程;
    

    main函数

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

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

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

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

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

    #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种,分别是:

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

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

    pthread_exit()和return:

    return

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

    pthread_exit()

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

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

    pthread_exit()和return()的区别

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

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

    pthread_cancel:

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

    
    int pthread_cancel(pthread_t thread);
    
    

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

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

    pthread_exit(PTHREAD_CANCELED);
    

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

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

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

    4. detach()

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

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

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

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

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

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

    查看绑定情况

    taskset -p pid
    

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

    启动时绑定

    taskset -c xxx,yyy ./pcie_perf&
    

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

    启动后绑定

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

    代码绑定

    ...
    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 (北京不北)
    
    获得免费答疑,长期技术交流。
    

    四、参考文献

    https://blog.csdn.net/qq_41854911/article/details/118718824

    相关文章

      网友评论

          本文标题:异构计算关键技术之多线程技术(四)

          本文链接:https://www.haomeiwen.com/subject/xccizdtx.html