一个被两个线程访问的共享队列的关键代码部分是什么?

8
假设我们有一个共享队列(使用数组实现),其中两个线程可以访问,一个用于从中读取数据,另一个用于向其中写入数据。现在,我有一个同步问题。我正在使用 Win32 API(EnterCriticalSection 等)来实现这个问题。
但是我的好奇心是,在队列的入队和出队操作中,临界区代码将是什么?
只因为两个线程正在使用共享资源?为什么我看不到任何问题呢:前端和后端都被维护着,所以当 ReaderThread 读取时,它可以从前端读取,而当 WriterThread 写入时,它可以轻松地写入到后端。
可能会出现什么潜在问题?

6
如果你只有一个入口,那么尾巴就等于头? - Jesus Ramos
1
应该没问题。如果你锁定了这个部分,读者将会获取到数据,而不会被写入者添加数据,读者也不会删除数据,导致下一个空槽位的偏移量对于写入者来说不正确。实际上,只要写入者从未更改过前面的内容,除了读者正在删除已经读取的内容的部分,你就不需要在它周围加上临界区,如果写入者同时尝试写入,那么读者会在写入者下次偏移之前更改下一个偏移量,因为它没有同步。 - Brain2000
1个回答

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 指针,并且在从其指向的槽中读取数据之前不递增 head 指针。因此,在至少进行一次调用enequeue之前,调用 dequeue 将不会返回有效结果,并且由于在 enqueue 中有检查,所以 tail 指针永远不会覆盖 head 指针。此外,只有一个线程递增 tail 指针,一个线程递增 head 指针,因此不存在从或向相同指针的共享读或写会创建同步问题,这需要锁定或某种类型的原子操作。请注意,本文中的 HTML 标签已保留。

太遗憾了,我只能给+1!好答案。 - Rajendra Uppal
这很酷,如果我不想等待队列“繁忙”。所以我必须写while(queue.enqueue(obj) != 0);来确保obj被推回队列,但这并不是很酷。 - ali_bahoo
2
@sad_man:如果队列已满/空,您可以在入队/出队时阻止生产者/消费者,并使用事件唤醒相应的线程。 - ChrisWue
@ChrisWue: 我认为使用事件会给问题增加几乎相同的复杂性(甚至更多)。同时,使用临界区是更高效的解决方案。 - ali_bahoo
1
如果您正在运行多CPU环境,则自旋锁将是最快的解决方案。自旋锁只是一种在原子变量上进行忙等待的形式。这种解决方案避免了必须由内核仲裁的较慢的锁,只有在队列为空或已满时才需要任何忙等待。例如,如果此队列将用作快速数据管道中的消息队列,其中需要最快的入队和出队操作以减少延迟,那么您几乎永远不会遇到长时间的忙等待...您绝对不想接触内核。 - Jason
C++有一个bool类型。请使用它。 - Puppy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接