【问题标题】:Are there race conditions in this producer-consumer implementation?在这个生产者-消费者实现中是否存在竞争条件?
【发布时间】:2016-01-15 06:11:59
【问题描述】:

在操作系统概念(Silberschatz,第 9 版)的第 3.4.1 节中,作者提出了生产者-消费者问题并给出了以下使用循环缓冲区的实现(第 125、126 页)。

//Shared variables

#define BUFFER SIZE 10
struct Item;
Item buffer[BUFFER SIZE];
int in = 0, out = 0;

//buffer is empty when  in == out
//buffer is full when   (in + 1) % BUFFER SIZE == out

//Producer
while (true)
{
    Item next_produced = /*produce item here*/;

    while (((in + 1) % BUFFER SIZE) == out) ; //do nothing

    buffer[in] = next_produced;
    in = (in + 1) % BUFFER SIZE;
}

//Consumer
while (true)
{   
    while (in == out) ;  //do nothing

    Item next_consumed = buffer[out];
    out = (out + 1) % BUFFER SIZE;

    //consume the item in next_consumed here
}

书上说:

此插图未解决的一个问题涉及 生产者进程和消费者进程都试图 同时访问共享缓冲区。

我看不到生产者和消费者会同时访问相同缓冲区元素的情况。

我的问题是:如果生产者和消费者在两个线程中运行,那么这个实现中是否存在竞争条件或其他同步问题?

【问题讨论】:

    标签: c multithreading producer-consumer


    【解决方案1】:

    有很多可能性

    1. 最明显的:如果有 2 个生产者生产数据。假设缓冲区中只有 1 个可用空间,两个生产者线程都可以通过 while (in + 1) % BUFFER SIZE) == out 并尝试放入缓冲区。这可能会导致缓冲区损坏或数据丢失

    2. 即使只有 1 个消费者和 1 个生产者,仍然存在一些不太明显的问题。例如,编译器可能会重新排列行

      buffer[in] = next_produced;
      in = (in + 1) % BUFFER SIZE;
      

      使in的更新早于buffer的更新,导致消费者访问未初始化的数据。

    【讨论】:

    • 请详细说明#2 compiler may rearrange the lines。简单地交换行显然会产生不同的行为,所以我猜你的意思是“编译器可能会将其重新排列为{ int temp = in; in = (in + 1) % BUFFER SIZE; buffer[temp] = next_produced; }”。但是,如果in 是越界索引(这可能导致例如引发异常),则此类重写将具有不同的 UB。在原始代码中,UB 处的in 将是无效的,在重写中它将是有效的。 C标准真的允许这样的重写吗?
    • 相当多的语言允许编译器为了优化而重新排序指令。只是谷歌的“c++ 编译器重新排序指令优化”会给你很多信息。
    • 阿德里安,我认为这种重组不可能发生。编译器只允许重新排列不会改变单线程系统中的行为。如果你交换两行,你最终会在单线程系统上得到不同的行为。
    • 我不是语言 律师所以我把这个作为一个单独的问题问here
    【解决方案2】:

    无法保证在修改inout 之前会看到对buffer[x] 的写入

    所以假设只有一个读取器和一个写入器,那么 in、out 变量分别在一个线程中进行修改。

    buffer[in] = next_produced;
    in = (in + 1) % BUFFER SIZE;
    

    可能会在阅读器中看到排序错误,导致阅读器看到 in 移动了,但是 buffer[in] 的旧值

    Item next_consumed = buffer[out];
    out = (out + 1) % BUFFER SIZE;
    

    可能由编译器或处理器错误排序,允许生产者写入完整队列,在next_consumed 读取值之前覆盖buffer[out] 的值。

    【讨论】:

      猜你喜欢
      • 2018-06-03
      • 1970-01-01
      • 2019-04-01
      • 2020-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多