假设我们有一个共享队列(使用数组实现),两个线程可以访问,一个用于从中读取数据,另一个用于向其写入数据。现在,我遇到了同步问题。我正在使用Win32 API(EnterCriticalSection等)实现这一点。
但我的好奇心是队列的入队和出队操作中的关键部分代码是什么?
仅仅因为,两个线程正在使用共享资源?为什么我无法看到任何问题:前端和后端都被维护,因此,当ReaderThread读取时,它可以从前端读取,当WriterThread写入时,它可以轻松写入后端。
可能会出现哪些潜在问题?
答案 0 :(得分:6)
对于单个生产者/消费者循环队列实现,实际上不需要锁。只需设置一个条件,如果队列已满,则生产者无法写入队列,如果队列为空,则消费者无法从队列中读取。生成器也将始终写入指向队列中第一个可用空槽的tail
指针,并且消费者将从表示队列中第一个未读槽的head
指针读取。
您的代码可能类似于以下代码示例(注意:我假设在初始化队列中tail == head
,并且两个指针都声明为volatile
,以便优化编译器不会重新在给定的线程中对操作序列进行排序。在x86上,由于体系结构的强大内存一致性模型,不需要内存屏障,但是在内存一致性模型较弱的其他体系结构中,这将会发生变化,其中需要内存障碍):
queue_type::pointer queue_type::next_slot(queue_type::pointer ptr);
bool queue_type::enqueue(const my_type& obj)
{
if (next_slot(tail) == head)
return false;
*tail = obj;
tail = next_slot(tail);
return true;
}
bool queue_type::dequeue(my_type& obj)
{
if (head == tail)
return false;
obj = *head;
head = next_slot(head);
return true;
}
函数next_slot
只是递增head
或tail
指针,以便它返回指向数组中下一个插槽的指针,并说明任何数组环绕功能。
最后,我们保证单个生产者/消费者模型中的同步,因为我们不会增加tail
指针,直到它将数据写入它指向的槽中,并且我们不增加{{1指针,直到我们从它指向的插槽中读取数据。因此,在至少调用head
之前,对dequeue
的调用不会返回有效,并且enequeue
指针永远不会覆盖tail
指针,因为签入head
。此外,只有一个线程正在递增enqueue
指针,并且一个线程正在递增tail
指针,因此对于同一指针的共享读取或写入没有问题会产生同步问题需要锁定或某种类型的原子操作。