message queue的设计

为了在各线程之间高效的传递消息,必须设计一种高效率的消息队列,传统的做法是mutex加queue,这种做法在每次执行push和pop时都要加锁,

效率相对较低。其次还有使用循环队列,可以做到完全无锁,但只能实现1:1的消息传递。还有一些lock-free队列的实现,但基于其实现的相对复杂

性,我不打算使用。

我的队列设计是使用tls维护一个local list,每个线程执行push时,首先将元素放入属于本线程的local list中,此时是无需加锁的,然后检查队列中元素

的总数,如果发现总数超过一个阀值,则将local list中的所有元素一次性提交到share list中,此时需要加锁,share list中的元素是对全局可见的。

当读者执行pop操作时,首先从检查自己的local list中是否有元素,如果有就返回一个,如果没有则尝试从share list中将所有元素同步到自己的local list

中.

local list和message queue的结构如下:

struct per_thread_struct
{
    list_node   next;
    struct double_link_node block;
    struct link_list *local_q;
    condition_t cond;
};

struct mq
{
    uint32_t           push_size;
    pthread_key_t      t_key;
    mutex_t            mtx;
    struct double_link blocks;
    struct link_list  *share_list;
    struct link_list  *local_lists;

};

对于push操作,提供了两个接口:

void mq_push(mq_t,struct list_node*);
void mq_push_now(mq_t,struct list_node*);

mq_push将元素插入local list但只有当local list中的元素到达一定阀值时才会执行提交操作mq_sync_push.

而mq_push_now将元素插入local list之后马上就会执行提交操作.

然后还有一个问题,如果local list中的元素较长时间内都达不到阀值,会导致消息传递的延时,所以提供了mq_force_sync函数,此函数的作用是

强制将执行一次提交操作,将local list中的所有元素提交到share list中去。producer线程可在其主循环内以固定的频率执行mq_force_sync,将一个

时间循环内剩余未被提交的消息提交出去.

下面贴下测试代码:

#include <stdio.h>
#include <stdlib.h>
#include "KendyNet.h"
#include "thread.h"
#include "SocketWrapper.h"
#include "atomic.h"
#include "SysTime.h"
#include "mq.h"

list_node *node_list1[5];
list_node *node_list2[5];
mq_t mq1;

void *Routine1(void *arg)
{
    int j = 0;
    for( ; ; )
    {
        int i = 0;
        for(; i < 10000000; ++i)
        {
            mq_push(mq1,&node_list1[j][i]);
        }
        mq_force_sync(mq1);
        j = (j + 1)%5; 
        sleepms(100);

    }
}

void *Routine3(void *arg)
{
    int j = 0;
    for( ; ; )
    {
        int i = 0;
        for(; i < 10000000; ++i)
        {
            mq_push(mq1,&node_list2[j][i]);
        }
        mq_force_sync(mq1);
        j = (j + 1)%5; 
        sleepms(100);

    }
}

void *Routine2(void *arg)
{
    uint64_t count = 0;
    uint32_t tick = GetCurrentMs();
    for( ; ; )
    {
        list_node *n = mq_pop(mq1,50);
        if(n)
        {
            ++count;
        }
        uint32_t now = GetCurrentMs();
        if(now - tick > 1000)
        {
            printf("recv:%d\n",(count*1000)/(now-tick));
            tick = now;
            count = 0;
        }
    }
}


int main()
{
    int i = 0;
    for( ; i < 5; ++i)
    {
        node_list1[i] = calloc(10000000,sizeof(list_node));
        node_list2[i] = calloc(10000000,sizeof(list_node));
    }
    mq1 = create_mq(4096);
    init_system_time(10);
    thread_t t1 = create_thread(0);
    start_run(t1,Routine1,NULL);

    thread_t t3 = create_thread(0);
    start_run(t3,Routine3,NULL);    

    thread_t t2 = create_thread(0);
    start_run(t2,Routine2,NULL);

    getchar();

    return 0;
}

因为主要是测试mq的效率,所以预先生成了1亿个消息,分为两个写者一个读者,两个写者循环不断的发消息,每发送1000W休眠一小会.

读者仅仅是从mq中pop一个消息出来,然后更新统计值.在我的i5 2.93双核台式机上运行rhel 6虚拟机,每秒pop出来的消息数量大概在8000W上下。

这个数据足已满足任何高性能的应用需求了.

https://github.com/sniperHW/KendyNet/blob/master/src/kn_msgque.c
posted @ 2012-10-18 16:22  sniperHW  阅读(6743)  评论(13编辑  收藏  举报