我正在尝试创建一个线程安全的队列,而在大部分操作中
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
函数?
答案 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
来解决它吗?等