简体   繁体   English

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

[英]Thread-safe implementation of is_empty in a queue datastructure

I am trying to create a thread-safe queue, and while the bulk of the operations 我试图创建一个线程安全的队列,而大量的操作

 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));

I am finding one to be particularily elusive, 我发现一个特别难以捉摸的人,

 bool queue_is_empty(const queue *q);

In particular if the function definition is 特别是如果函数定义是

 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;
 }

then there is a possibility of a race-condition. 那么就有可能出现竞争状况。 The last comment 最后的评论

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 这里有可能出现竞争状况,此时队列实际上可能为空,因此返回值会产生误导

describes the issue. 描述问题。

I have defined my datastructure as such, 我已经定义了我的数据结构,

 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;

And for the implementation of the other functions I have worked around the inadequate queue_is_empty function by only checking the value of q->is_empty since I have already taken a lock on the structure before checking the value. 对于其他功能的实现,我通过仅检查q->is_empty的值来解决不适当的queue_is_empty函数,因为在检查该值之前我已经对结构进行了锁定。

The queue_dequeue function serves as an example of where one would want to know if the queue is empty. queue_dequeue函数用作一个示例,说明人们想知道队列是否为空。 See the first if -statement. 请参阅第一个if -statement。

 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; 
 }

How do I expose a thread-safe queue_is_empty function? 如何公开线程安全的queue_is_empty函数?

Basically, your function is already correct. 基本上,您的功能已经正确。 It's just that the result is outdated the moment you release the lock. 只是当您释放锁时结果已经过时了。 In a multi-threaded program, asking whether a concurrent queue is empty is pretty pointless as another thread could enter data into the queue any moment. 在多线程程序中,询问并发队列是否为空是毫无意义的,因为另一个线程可以随时将数据输入队列。

You've just discovered the reason why locking, threading and concurrency in general is hard. 您刚刚发现了通常很难进行锁定,线程化和并发的原因。 It's easy to create a few wrapper functions that obtain and release locks and prevent crashes from data corruption, it's actually hard to get locking right when you depend on state from earlier accesses. 创建一些包装函数以获取和释放锁并防止由于数据损坏而导致的崩溃很容易,实际上,当您依赖早期访问的状态时,很难正确地获得锁定。

I'd argue that the function queue_is_empty is incorrect because it exists at all. 我认为函数queue_is_empty是不正确的,因为它根本就存在。 It cannot possibly return a useful value because, as you discovered, the return value is stale before you return it. 它可能无法返回有用的值,因为正如您所发现的,返回值在返回之前是陈旧的。 Since the function cannot return a useful return value it should not exist. 由于该函数无法返回有用的返回值,因此它不应该存在。 And this is where you need to think hard about the API you provide. 这是您需要认真考虑所提供的API的地方。

One option is to make the locking the responsibility of the callers. 一种选择是使锁定锁定呼叫者的责任。 The caller could have something like: 呼叫者可能有类似以下内容:

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

You will then run into issues with error handling and returns from functions, lock state changes around functions not part of the queue interface, etc. And as soon as you have more than one of those locks you need to manage lock ordering to prevent deadlocks. 然后,您将遇到错误处理和从函数返回的问题,围绕不属于队列接口一部分的函数的锁状态更改,等等。而且,一旦拥有多个这些锁之一,就需要管理锁顺序以防止死锁。

Another option is to reduce the API to only provide safe operations. 另一个选择是减少API以仅提供安全的操作。 Enqueue and dequeue work correctly. 入队和出队工作正常。 Do you actually need is_empty? 您实际上需要is_empty吗? What is it good for? 到底有什么好处呢? Is it just to avoid waiting for a queue element when the queue is empty? 是否只是为了避免在队列为空时等待队列元素? Can you not solve it with dequeue_without_waiting instead? 您能不能用dequeue_without_waiting解决呢? Etc. 等等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM