队列数据结构中is_empty的线程安全实现

时间:2016-06-29 09:16:38

标签: c data-structures thread-safety mutex race-condition

我正在尝试创建一个线程安全的队列,而在大部分操作中

 queue *queue_create_empty(void);
 void queue_enqueue(queue *q, const void *value, const size_t value_size);
 void *queue_dequeue(queue *q);
 void queue_destroy(queue *q, void (*freefunc)(void *value));

我发现一个特别难以捉摸,

 bool queue_is_empty(const queue *q);

特别是如果函数定义是

 bool queue_is_empty(const queue *q) {
     assert(q);

     pthread_mutex_lock(q->mutex);
     bool ret = q->is_empty;
     pthread_mutex_unlock(q->mutex);

     /* 
      * Here there is a possibility of a race condition, the queue may actually
      * be empty at this point, and therefore the return value is misleading
      */
     return ret;
 }

然后有可能出现竞争条件。最后的评论

  

这里存在竞争条件的可能性,实际上可能是队列   此时为空,因此返回值具有误导性

描述了这个问题。

我已经定义了我的数据结构,

 typedef struct queue {
     element *head;
     element *tail;

     /* Lock for all read/write operations */
     pthread_mutex_t *mutex;

     /* Condition variable for whether or not the queue is empty */
     /* Negation in variable names is poor practice but it is the */
     /* only solution here considering the conditional variable API */
     /* (signal/broadcast) */
     pthread_cond_t *not_empty;

     /* Flag used to gauge when to wait on the not_empty condition variable */
     bool is_empty;

     /* A flag to set whenever the queue is about to be destroyed */
     bool cancel;
 } queue;

为了实现我已经解决过的其他功能 只检查值的queue_is_empty功能不足 q->is_empty因为我在检查值之前已经锁定了结构。

queue_dequeue函数用作人们想知道队列是否为空的地方的示例。请参阅第一个if - 声明。

 void *queue_dequeue(queue *q) {
     assert(q);

     void *value = NULL;
     pthread_mutex_lock(q->mutex);

     /* We have a mutex-lock here, so we can atomically check this flag */
     if (q->is_empty) {
         /* We do not have to check the cancel flag here, if the thread is awoken */
         /* in a destruction context the waiting thread will be awoken, the later */
         /* if statement checks the flag before modification of the queue */

         /* This allows other threads to access the lock, thus signalling this thread */
         /* When this thread is awoken by this wait the queue will not be empty, or */
         /* the queue is about to be destroyed */
         pthread_cond_wait(q->not_empty, q->mutex);
     }

     /* We have a mutex lock again so we may atomically check both flags */
     if (!q->cancel && !q->is_empty) {
         value = q->head->value;
         if (q->head->next) {            
             q->head = q->head->next;
             free(q->head->previous);

             /* Make dereferencing dangling pointers crash the program */
             q->head->previous = NULL;
         } else {
             free(q->head);
             q->head = NULL;
             q->is_empty = true;
         }        
     }

     pthread_mutex_unlock(q->mutex);
     return value; 
 }

如何公开线程安全的queue_is_empty函数?

2 个答案:

答案 0 :(得分:2)

基本上,您的功能已经正确。只是你释放锁定时结果已经过时了。在一个多线程程序中,询问并发队列是否为空是没有意义的,因为另一个线程可以随时将数据输入队列。

答案 1 :(得分:2)

您刚刚发现锁定,线程和并发一般很难的原因。创建一些获取和释放锁并防止数据损坏崩溃的包装函数很容易,当你依赖早期访问的状态时,实际上很难锁定。

我认为函数queue_is_empty不正确,因为它根本就存在。它不可能返回有用的值,因为正如您所发现的那样,返回值在返回之前是陈旧的。由于函数不能返回有用的返回值,因此它不应该存在。这就是您需要仔细考虑所提供的API的地方。

一种选择是使锁定对呼叫者负责。调用者可以有类似的东西:

queue_lock(q);
if (!queue_is_empty(q))
    do_something(q);
queue_unlock(q);

然后,您将遇到错误处理和函数返回问题,锁定不属于队列接口的函数的状态更改等。一旦您有多个锁,您需要管理锁定顺序到防止死锁。

另一个选择是减少API以仅提供安全操作。正确排队和出列工作。你真的需要is_empty吗?到底有什么好处呢?是否只是为了避免在队列为空时等待队列元素?你不能用dequeue_without_waiting来解决它吗?等