测试代码优化记录(一)---多线程增加信号量

549 阅读6分钟

1.背景

最近准备通过多线程申请和释放内存,测试多核并发的场景,之前的用例两个线程是不同优先级的,现在设计的新用例是相同优先级,希望他们自己去申请和释放。由于我还比较菜鸟,第一版的代码写的很简陋,纯新建了四个线程,相同优先级,两个申请两个释放,跑起来会出现释放的线程一下子全运行完了,后面才开始申请内存。

丑陋的源代码:

/* 线程1入口 */
static rt_void_t thread1_mp_alloc(jx_void_t *parameter)
{
    jx_mw_mb_handle_t* handle = (jx_mw_mb_handle_t*)parameter;
    int i;

    for (i = 0 ; i < 200 ; i++)
    {
        rt_kprintf("thread1 try to alloc block\n");
        if (ptr[i] == JX_NULL)
        {
            if (jx_mw_mb_alloc(handle, 1920*1080, &ptr[i], JX_NULL) != JX_OK) {
                rt_kprintf("jx_mw_mb_alloc failed\n");
            }
            if (ptr[i] != JX_NULL)
                rt_kprintf("allocate No.%d, addr: 0x%08x\n", i, (unsigned int)ptr[i]);
        }
    }
}

/* 线程2入口*/
static jx_void_t thread2_mp_release(jx_void_t *parameter)
{
    jx_thread_sleep_ms(1000);
    jx_mw_mb_handle_t* handle = (jx_mw_mb_handle_t*)parameter;
    int i;

    for (i = 0; i < 200 ; i++)
    {
        rt_kprintf("%uthread2 try to release block\n",i);
        /* 释放所有分配成功的内存块 */
        if (ptr[i] != JX_NULL)
        {
            rt_kprintf("release block %d\n", i);
            if (jx_mw_mb_free(handle,  ptr[i], JX_NULL) != JX_OK) {
                rt_kprintf("jx_mw_mb_free failed\n");
            }
            ptr[i] = JX_NULL;
        }
    }
}



jx_void_t mb_func_test_smp1()
{
    jx_err_t err;
    jx_mw_mb_conf_t config;
    memset(&config, 0, sizeof(config));
    strncpy(config.name, "isp", sizeof(config.name) - 1);
    config.max_pool_count = 1;
    config.mb_pools[0].block_size = 1920*1080;
    config.mb_pools[0].block_count = 4;


    if ((err = jx_mw_mb_set_conf(&config)) != JX_OK) {
       // rt_kprintf("jx_mw_mb_set_conf failed err = %lu\n", err);
        return;
    }

    jx_mw_mb_handle_t* handle = JX_NULL;

    if((err = jx_mw_mb_init(&handle, "isp")) != JX_OK) {
       // rt_kprintf("jx_mw_mb_init failed, err = %ld\n", err);
        return;
    }
    // list_thread();

    /* 创建线程1:申请内存池 */
    tid1 = rt_thread_create("thread1", thread1_mp_alloc, handle,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid1 != JX_NULL)
        rt_thread_startup(tid1);

    /* 创建线程2:释放内存池*/
    tid2 = rt_thread_create("thread2", thread2_mp_release, handle,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid2 != JX_NULL)
        rt_thread_startup(tid2);

     /* 创建线程3:申请内存池 */
    tid3 = rt_thread_create("thread3", thread3_mp_alloc, handle,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid3 != JX_NULL)
        rt_thread_startup(tid3);
    list_thread();
    /* 创建线程4:释放内存池*/
    tid4 = rt_thread_create("thread4", thread4_mp_release, handle,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid4 != JX_NULL)
        rt_thread_startup(tid4);

    list_thread();
    // while(1){
    //     jx_thread_sleep_ms(100000);
    // }
}

2. 优化----增加信号量

我想着不能这么随意,这样运行跑不出效果。于是决定增加信号量。

信号量简介

信号量(semaphore)是一种用于多线程同步的机制。信号量维护一个计数器,表示可以访问资源的线程数量。线程可以对信号量进行两种操作:

  1. P操作(wait, take) :等待信号量计数器大于零,然后将计数器减一。如果计数器为零,线程将阻塞直到计数器大于零。
  2. V操作(signal, release) :将信号量计数器加一,并唤醒等待在该信号量上的线程(如果有的话)。

在代码中的应用

在代码中,我们使用了两个信号量sem_allocsem_release,分别用于控制内存池的申请和释放操作。具体来说:

  • sem_alloc:控制申请内存池的线程。
  • sem_release:控制释放内存池的线程。

线程与信号量的交互

  1. 初始状态
    • sem_alloc的初始值为1,表示可以有一个线程进行内存池申请操作。
    • sem_release的初始值为0,表示没有线程可以进行内存池释放操作,因为还没有任何内存块被申请。
  2. 线程1和线程3(申请内存池)
    • 这两个线程都会在sem_alloc信号量上进行take操作(P操作),等待计数器大于零。
    • sem_alloc的计数器大于零时,线程1或线程3会被唤醒,并将计数器减一。
    • 申请内存池操作完成后,这些线程会对sem_release信号量进行release操作(V操作),将计数器加一,允许释放内存池的线程运行。
  3. 线程2和线程4(释放内存池)
    • 这两个线程会在sem_release信号量上进行take操作,等待计数器大于零。
    • sem_release的计数器大于零时,线程2或线程4会被唤醒,并将计数器减一。
    • 释放内存池操作完成后,这些线程会对sem_alloc信号量进行release操作,将计数器加一,允许申请内存池的线程运行。

同步过程详解

  1. 初始阶段
    • sem_alloc = 1,sem_release = 0。
    • 线程1或线程3首先执行,成功申请内存池后,sem_alloc = 0,sem_release = 1。
  2. 释放内存池
    • 线程2或线程4被唤醒,执行释放内存池操作后,sem_alloc = 1,sem_release = 0。
    • 释放操作完成,唤醒等待在sem_alloc上的线程(线程1或线程3)。
  3. 交替执行
    • 这个过程交替进行,确保申请和释放操作不会同时进行,从而避免竞争条件和资源冲突。

通过使用信号量,线程间可以实现精确的同步,保证操作的有序进行。

修改方法:增加线程间的同步

修改后代码:

#include <rtthread.h>
#include <string.h>

#define THREAD_STACK_SIZE 1024
#define THREAD_PRIORITY 25
#define THREAD_TIMESLICE 10

/* 信号量 */
static rt_sem_t sem_alloc;
static rt_sem_t sem_release;

void thread1_mp_alloc(void* parameter)
{
    jx_mw_mb_handle_t* handle = (jx_mw_mb_handle_t*)parameter;

    while (1)
    {
        /* 等待申请信号量 */
        rt_sem_take(sem_alloc, RT_WAITING_FOREVER);

        /* 申请内存池 */

        /* 释放释放信号量 */
        rt_sem_release(sem_release);
    }
}

void thread2_mp_release(void* parameter)
{
    jx_mw_mb_handle_t* handle = (jx_mw_mb_handle_t*)parameter;

    while (1)
    {
        /* 等待释放信号量 */
        rt_sem_take(sem_release, RT_WAITING_FOREVER);

        /* 释放内存池 */

        /* 释放申请信号量 */
        rt_sem_release(sem_alloc);
    }
}

void thread3_mp_alloc(void* parameter)
{
    jx_mw_mb_handle_t* handle = (jx_mw_mb_handle_t*)parameter;

    while (1)
    {
        /* 等待申请信号量 */
        rt_sem_take(sem_alloc, RT_WAITING_FOREVER);

        /* 申请内存池的操作 */

        /* 释放释放信号量 */
        rt_sem_release(sem_release);
    }
}

void thread4_mp_release(void* parameter)
{
    jx_mw_mb_handle_t* handle = (jx_mw_mb_handle_t*)parameter;

    while (1)
    {
        /* 等待释放信号量 */
        rt_sem_take(sem_release, RT_WAITING_FOREVER);

        /* 释放内存池的操作 */

        /* 释放申请信号量 */
        rt_sem_release(sem_alloc);
    }
}

jx_void_t mb_func_test_smp1()
{
    jx_err_t err;
    jx_mw_mb_conf_t config;
    memset(&config, 0, sizeof(config));
    strncpy(config.name, "isp", sizeof(config.name) - 1);
    config.max_pool_count = 1;
    config.mb_pools[0].block_size = 1920*1080;
    config.mb_pools[0].block_count = 4;

    if ((err = jx_mw_mb_set_conf(&config)) != JX_OK) {
        return;
    }

    jx_mw_mb_handle_t* handle = JX_NULL;

    if((err = jx_mw_mb_init(&handle, "isp")) != JX_OK) {
        return;
    }

    /* 初始化信号量 */
    sem_alloc = rt_sem_create("sem_alloc", 1, RT_IPC_FLAG_FIFO);
    sem_release = rt_sem_create("sem_release", 0, RT_IPC_FLAG_FIFO);

    if (sem_alloc == JX_NULL || sem_release == JX_NULL) {
        return;
    }

    /* 创建线程1:申请内存池 */
    rt_thread_t tid1 = rt_thread_create("thread1", thread1_mp_alloc, handle,
                                        THREAD_STACK_SIZE,
                                        THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid1 != JX_NULL)
        rt_thread_startup(tid1);

    /* 创建线程2:释放内存池*/
    rt_thread_t tid2 = rt_thread_create("thread2", thread2_mp_release, handle,
                                        THREAD_STACK_SIZE,
                                        THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid2 != JX_NULL)
        rt_thread_startup(tid2);

    /* 创建线程3:申请内存池 */
    rt_thread_t tid3 = rt_thread_create("thread3", thread3_mp_alloc, handle,
                                        THREAD_STACK_SIZE,
                                        THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid3 != JX_NULL)
        rt_thread_startup(tid3);

    /* 创建线程4:释放内存池*/
    rt_thread_t tid4 = rt_thread_create("thread4", thread4_mp_release, handle,
                                        THREAD_STACK_SIZE,
                                        THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid4 != JX_NULL)
        rt_thread_startup(tid4);

    list_thread();

    /*清除信号量*/
    rt_sem_delete(sem_alloc);
    rt_sem_delete(sem_release);
}