队列是最为常见的的数据结构之一,每个合格的程序员都应该掌握如何实现一个队列,这里我讨论如何实现一个线程安全的可阻塞队列的实现。

完整代码请参考jdc_sdl_queue.c

队列结构的定义

struct JDCSDLPacketQueue {
    void *first_pk;//链表头指针
    void *last_pk;//链表尾指针
    int size;//当前size    
    SDL_mutex *mutex;//互斥锁
    SDL_cond *cond;//条件
};

队列的基本操作

经典的队列要要支持几个基本操作 + Push 从末尾添加元素。 + pop 从头部移除元素,并返回头部的指针。 + front 获取头部的指针。 从队列的定义我们可以推断出使用单向链表来实现是非常方便的。我们需要定义一个链表的结构来就行数据的存储。我的链表node定义如下

typedef struct JDCQueueNode {
    struct JDCQueueNode *next;
    void *data;
}JDCQueueNode;

我将数据指针存在Node的data字段,而data是一个通用指针,我们可以存放任意数据的指针。

让我们来看看队列相关方法的定义.

//为Queue动态分配内存。
JDCSDLPacketQueue *jdc_packet_queue_alloc();
//初始化队列,主要是初始化锁相关的内容。
void jdc_packet_queue_init(JDCSDLPacketQueue *queue);
//获取当前的size
int jdc_packet_queue_size(JDCSDLPacketQueue *queue);
//push
int jdc_packet_queue_push(JDCSDLPacketQueue *queue , void *data);
//front
void *jdc_packet_queue_front(JDCSDLPacketQueue *queue);
//pop
void *jdc_packet_queue_pop(JDCSDLPacketQueue *queue);
//这个方法尝试获取队列头部的元素。Block参数为1时,如果不存在则会阻塞当前线程,直到有元素为止。
int jdc_packet_queue_get_packet(JDCSDLPacketQueue *queue , void **data , int blockThread);

队列的实现

队列的实现主要是对链表的操作,我们先来看看push的操作:

int jdc_packet_queue_push(JDCSDLPacketQueue *queue , void *date)
{
    //分配一个新的链表节点来存储数据
    JDCQueueNode *listNode = (JDCQueueNode *)malloc(sizeof(JDCQueueNode));
    
    if (!listNode) {
        return -1;
    }
    
    listNode->data = data;
    listNode->next = NULL;

    //上锁处理多线程的问题    
    SDL_LockMutex(queue->mutex);
    
    //指针操作,如果是第一个节点直接复制到头指针
    //否则将新节点放到最后一个节点的next    
    if (queue->first_pk == NULL) {
        queue->first_pk = listNode;
    }else{
        ((JDCQueueNode *)queue->last_pk)->next = listNode;
    }

    //更新尾指针 
    queue->last_pk = listNode;
    queue->size ++;
    
    //发信号唤醒等待线程    
    SDL_CondSignal(queue->cond);
    //解锁
    SDL_UnlockMutex(queue->mutex);
    
    return 0;
}

然后是pop操作,将第一个元素拿出来,然后将其从队列中移除:

void *jdc_packet_queue_pop(JDCSDLPacketQueue *queue)
{
    void *data = NULL;
    //加锁
    SDL_LockMutex(queue->mutex);
    
    if (queue->first_pk) {
        //取出第一个元素
        JDCQueueNode *firstPkl = queue->first_pk;
        data = firstPkl->data;
        //更新指针
        queue->first_pk = firstPkl->next;
        queue->size--;
        
        //因为链表node是动态分配内存,需要释放
        free(firstPkl);
    }
    
    SDL_UnlockMutex(queue->mutex);
    
    return data;
}

再来看可阻塞的的获取数据方法:

//这个方法尝试获取队列第一个元素,如果存在则立即返回。如果队列为空而且Block为1的时候会阻塞
int jdc_packet_queue_get_packet(JDCSDLPacketQueue *queue , void **data , int block)
{
    int ret;
    
    SDL_LockMutex(queue->mutex);
    //在这个无限循环中我尝试获取第一个数据
    while (1) {
        if (queue->quit) {
            ret = -1;
            break;
        }
        
        //如果拿到数据立即返回,否则挂起等待信号      
        if (queue->first_pk) {
            *data = jdc_packet_queue_pop(queue);
            ret = 1;
            break;
        }else if(block){
            SDL_CondWait(queue->cond, queue->mutex);
        }else{
            ret = 0;
            break;
        }
    }
    
    SDL_UnlockMutex(queue->mutex);
    
    return ret;
}

总结

这样我们就实现了一个通用的可阻塞等待队列了。我们可以在没有数据的时候先将线程挂起,有数据的时候自动唤醒继续执行。完整代码请参考jdc_sdl_queue.c