【问题标题】:Single producer/consumer circular buffer which only blocks consumer单个生产者/消费者循环缓冲区,仅阻塞消费者
【发布时间】:2017-04-07 00:39:32
【问题描述】:

我想实现一个具有单个生产者和单个消费者的缓冲区,其中只有消费者可能被阻止。这里的重要细节是,如果队列已满,生产者可以删除更新。

我考虑过转换一个无等待的实现,但乍一看似乎没有简单的方法可以在不丢失通知的情况下通知消费者新数据已到达。所以我选择了下面非常简单的方法,使用计数信号量(为清楚起见,省略了一些错误处理细节):

Object ar[SIZE];
int head = 0, tail = 0;
sem_t semItems; // initialized to 0

void enqueue(Object o) {
    int val;
    sem_getvalue(&semItems, &val);
    if (val < SIZE - 1) {
        ar[head] = o;
        head = (head + 1) % SIZE;
        sem_post(&semItems);
    }
    else {
       // dropped
    }
}
Object dequeue(void) {
    sem_wait(&semItems);
    Object o = ar[tail];
    tail = (tail + 1) % SIZE;
    return o;
}

此代码是否存在任何安全问题?我很惊讶在流行文献中没有看到类似的实现。另一个问题是sem_post() 是否会阻塞(在 linux 的后台调用futex_wake())。当然也欢迎更简单的解决方案。

编辑:编辑代码以在读者和作者之间留出空间(参见 Mayurk 的回复)。

【问题讨论】:

  • 我能知道你为什么只需要阻止消费者吗?
  • @SumitGemini 当流量很大时,消费者应该比生产者运行得慢,因为生产者与硬件对话。这只是我正在研究的一种可能方法,可能是隐式 CAS 循环实际上使情况变得更糟。
  • @SumitGemini 刚刚发现了一个关于原生 C++ 信号量的提案,它可以更好地解释它:open-std.org/jtc1/sc22/wg21/docs/papers/2006/…

标签: c multithreading pthreads semaphore


【解决方案1】:

我可以在这个实现中看到一个问题。考虑以下顺序。

  1. 假设缓冲区已满,但尚未启动消费者。所以(head=0,tail=0,sem_val=SIZE)。
  2. dequeue() 从消费者线程调用。 sem_wait() 成功。所以就在那个实例(head=0,tail=0,sem_val=SIZE-1)。消费者开始阅读 ar[0]。
  3. 现在有一个线程切换。 enqueue() 从生产者线程调用。 sem_getvalue() 将返回 SIZE-1。所以生产者写在 ar[0]。

基本上我认为您需要对读写操作进行互斥保护。但是添加互斥锁可能会阻塞线程。所以我不确定你是否会从这个逻辑中得到预期的行为。

【讨论】:

  • 你是对的,但这不是通过牺牲缓冲区中的一个位置来轻松解决的,即确保 val
  • @wds 否。您可能随时会遇到此问题。示例(head=3,tail=3,sem_val=SOMTHING)。即使在这种情况下,如果上述操作顺序导致缓冲区损坏。
  • dequeue 只能在 enqueue 通过在信号量上发布“发布”后从缓冲区中的位置读取。我不明白你提到的情况怎么会存在。
  • 如果在sem_wait()之后,在尾值更新线程切换之前,就会出现这个问题。
  • 您的解决方案遇到了同样的问题。如果缓冲区已满,那么您可以拥有h=0, t=1, rem=size,执行sem_wait,现在enqueue() 将写入ar[1],而dequeue() 可能正在读取它。
猜你喜欢
  • 2019-06-13
  • 2019-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-21
相关资源
最近更新 更多