c语言数据结构应用-数组队列(无锁队列)在多线程中的使用

一、背景

上篇文章《c语言数据结构实现-数组队列/环形队列》讲述了数组队列的原理与实现,本文编写一个双线程进行速度测试

二、相关知识

多线程编程接口:

1) 创建线程 pthread_create 函数

SYNOPSIS
       #include 

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

       Compile and link with -pthread.
DESCRIPTION:
       The pthread_create() function starts a new thread in the calling process.  The new thread starts execution by invoking start_routine(); arg is passed as the sole argument of start_routine().
RETURN VALUE:
       On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.
2) 设置线程属性 pthread_attr_setdetachstate
SYNOPSIS
       #include 
       int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
       Compile and link with -pthread.

DESCRIPTION
       The  pthread_attr_setdetachstate() function sets the detach state attribute of the thread attributes object referred to by attr to the value specified in detachstate.  The detach state attribute determines whether a thread created using the thread attributes object attr will be created in a joinable or a detached state.

RETURN VALUE
       On success, these functions return 0; on error, they return a nonzero error number.
3) 线程锁 pthread_mutex_init、pthread_mutex_lock、pthread_mutex_unlock、pthread_mutex_destory

线程锁是用于多线程直接的同步,用来保护临界资源(数值队列),同样类似的功能有 Posix 的 sem信号量。

4) 条件变量 pthread_cond_init、pthread_cond_signal、pthread_cond_wait、pthread_cond_destroy

条件变量是同步线程的一种机制,它允许线程挂起,让出处理器等待其他线程向它发送信号,该线程收到该信号后被唤醒继续执行程序。

、实现

数据结构 test_t 为用户数据,instance_t 为程序上下文

typedef struct item_t
{
    int value;
} test_t;

typedef struct instance 
{
    sem_t sem_mutex;
    pthread_mutex_t thd_mutex;
    pthread_cond_t thd_cond;
    bufqueue_t queue;
} instance_t;

主线程创建子线程,并生产数据进行入列操作,顺序地生产 times 个 pitem 数据进行入列,生产完成后等待子线程消费

线程的工作方式分别有三种:线程完全异步的机制、使用sem保证实时线程同步、使用条件变量保持同步

int test_mutilthread(int times)
{
    int ret = FAILURE;
    int ix = 0;

    pthread_t tid = 0;
    pthread_attr_t attr;

    instance_t inst = {.queue = {0}};
    test_t *pitem = NULL;

#ifdef TEST_SEM
    printf("Sem sem_mutex mode\n");
    assert(SUCCESS == ( ret = sem_init(&inst.sem_mutex, 0, 0) ));
#elif defined(TEST_COND)
    printf("Pthread Cond mode\n");
    assert(SUCCESS == ( ret = pthread_mutex_init(&inst.thd_mutex, NULL) ));
    assert(SUCCESS == ( ret = pthread_cond_init(&inst.thd_cond, NULL) ));
#endif
    assert(SUCCESS == ( ret = bufqueue_create(&inst.queue, times, sizeof(test_t)) ));
    assert(SUCCESS == ( ret = pthread_attr_init(&attr) ));
    assert(SUCCESS == ( ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) ));
    assert(SUCCESS == ( ret = pthread_create(&tid, NULL, __do_customer, &inst) ));

    /* Producer */
    for ( ix = 1; ix < times; ix++ ) {
        assert(NULL != ( pitem = (test_t*)bufqueue_tail(&inst.queue) ));
        /* Set custom item */
        pitem->value = ix;
        assert(SUCCESS == ( ret = bufqueue_push(&inst.queue) ));
        printf("Enqueue        %u/%u\n", pitem->value, ix);
#ifdef TEST_SEM
        sem_post(&inst.sem_mutex);
#elif defined(TEST_COND)
        pthread_cond_signal(&inst.thd_cond);
#endif
    }

    while ( !bufqueue_isempty(&inst.queue) ) {
        printf("Wait dequeue...\n");
        usleep(300);
    }

    ret = SUCCESS;
_E1:
    pthread_attr_destroy(&attr);
    bufqueue_destroy(&inst.queue, free);
#ifdef TEST_SEM
    sem_destroy(&inst.sem_mutex);
#elif defined(TEST_COND)
    pthread_cond_destroy(&inst.thd_cond);
    pthread_mutex_destroy(&inst.thd_mutex);
#endif
    return ret;
}


子线程负责取队列中的数据,取成功 times 次后就退出

线程的工作方式分别也有三种:线程完全异步的机制、使用sem保证实时线程同步、使用条件变量保持同步

static void *__do_customer(void *args)
{
    instance_t *pinst = (instance_t *)args;
    test_t *pitem = NULL;

    u32 cnt = 1;
    u32 max = 0;

    ASSERT_FAIL(NULL, args);

    for ( max = pinst->queue.size; cnt < max; ) {
#ifdef TEST_SEM
        sem_wait(&pinst->sem_mutex);
#elif defined(TEST_COND)
        if ( bufqueue_isempty(&pinst->queue) ) {
            pthread_mutex_lock(&pinst->thd_mutex);
            pthread_cond_wait(&pinst->thd_cond, &pinst->thd_mutex);
            pthread_mutex_unlock(&pinst->thd_mutex);
            printf("Wait enqueue...\n");
            continue;
        }
#else
        if ( bufqueue_isempty(&pinst->queue) ) {
            usleep(300);
            printf("Wait enqueue...\n");
            continue;
        }
#endif
        assert(NULL != (pitem = (test_t*)bufqueue_pop(&pinst->queue) ));
        assert(cnt++ == pitem->value);
        printf("Dequeue        %u/%u\n", pitem->value, cnt);
    }

    printf("Dequeue Done\n");
_E1:
    return NULL;
}

main函数

int main(int argc, char *argv[])
{
    int ret = FAILURE;
    u32 times = 1024;

    if ( argc > 1 ) {
        times = atoi(argv[1]);
    }

    ret = test_mutilthread(times)
    printf("Test result:\t\t\t\t[%s]\n", ret ? "FAILURE" : "SUCCESS");
    exit(ret ? 1 : 0);
} 

注意以上写法为原型断言的写法,保证断言失败时程序立刻退出,仅为测试使用,不符合实际的代码规范


四、总结分析

测试硬件为Intel(R) Core(TM) i7-4500U CPU @ 1.80GHz

样本大小:100'000个

主线程得知队列空的情况后,加入一个时间统计的功能,分别得出一秒钟操作的速度:

  1. 线程完全异步:20000'000 次/每秒
  2. 使用sem保证实时线程同步:1960'000 次/每秒
  3. 使用条件变量保持同步:9000'000 次/每秒
数组队列在三种工作方式下均能成功执行,速度上为无锁的情况为最快,CPU占用也是最大(usleep轮询)
综合结果考虑来说使用条件变量正好可以避免 子线程过多地占用CPU进行轮询操作,速度也仅为无锁的一半


参考文章:

[1] Linux C++的多线程编程, http://www.cnblogs.com/youtherhome/archive/2013/03/17/2964195.html

[2] 多线程编程-互斥锁 http://blog.chinaunix.net/uid-21411227-id-1826888.html


你可能感兴趣的:(linux)