苹果文档中的GCD生产者-消费者解决方案有误?

16
在苹果的并发编程指南中的“迁移线程”一节中,有一个名为更改生产者-消费者实现的部分,声称可以使用GCD简化典型的多步骤pthread mutex +条件变量实现。通过使用调度队列,您可以将生产者和消费者的实现简化为单个调用。
dispatch_async(queue, ^{
  // Process a work item.
});

当您的生产者有工作要完成时,它只需将该工作添加到队列中,让队列处理该项任务。
生产者-消费者问题也被称为有界缓冲区问题,然而上述内容未提及缓冲区、其边界或消费者,更不用说阻塞生产者和消费者以避免过度/欠载。
这怎么可能是有效的解决方案呢?

但从性能的角度来看,在这种情况下,什么才是更好的选择呢?是同时运行生产者和消费者的两个线程,在系统初始化时启动,还是采用GCD路线,当生产者有更多数据要添加时,调度一个异步线程(因为调度线程也需要一定的时间)。 - ichthyocentaurs
队列就是缓冲区。 - James Bush
1个回答

24
在那份苹果文档中所描述的解决方案中:
  1. 没有缓冲区,因为不需要缓冲区;
  2. 系统负载是限制因素;
  3. 消费者是任务。
假设你有多个生产者和消费者,生产者将数据放入共享缓冲区,而消费者从该共享缓冲区读取数据。使用信号量或监视器来同步访问共享缓冲区,并且缓冲区大小是固定的,以便根据正在被消耗的速率限制正在被生产的数据量,从而限制生产者。
在Grand Central Dispatch下,消费者是分派到队列的任务。由于任务是Objective-C块,因此生产者不需要缓冲区来告诉消费者应该处理的数据:Objective-C块会自动捕获它们引用的对象。
例如:
// Producer implementation
while (…) {
    id dataProducedByTheProducer;

    // Produce data and place it in dataProducedByTheProducer
    dataProducedByTheProducer = …;

    // Dispatch a new consumer task
    dispatch_async(queue, ^{
        // This task, which is an Objective-C block, is a consumer.
        //
        // Do something with dataProducedByTheProducer, which is
        // the data that would otherwise be placed in the shared
        // buffer of a traditional, semaphore-based producer-consumer
        // implementation.
        //
        // Note that an Objective-C block automatically keeps a
        // strong reference to any Objective-C object referenced
        // inside of it, and the block releases said object when
        // the block itself is released.

        NSString *s = [dataProducedByTheProducer …];
    });
}

生产者可以放置与其所能生成的数据一样多的消费者任务。然而,这并不意味着GCD会以相同的速率触发消费者任务。GCD使用操作系统信息来控制根据当前系统负载执行的任务数量。生产者本身没有被限制,大多数情况下也不需要被限制,因为GCD具有固有的负载平衡。
如果实际上需要限制生产者,则一种解决方案是拥有一个主节点,它将分派n个生产者任务,并让每个消费者在完成工作后通知主节点(通过一个任务),主节点将再次分派另一个生产者任务。或者,消费者本身可以在完成后分派生产者任务。
具体回答您提出的问题:
生产者-消费者问题也称为有界缓冲区问题,但上述内容没有提到缓冲区。
不需要共享缓冲区,因为消费者是Objective-C块,它们自动捕获它们引用的数据。
它的边界。
GCD根据当前系统负载限制分派任务的数量。
或者消费者。
消费者是分派到GCD队列的任务。
更不用说为了避免过度/不足运行而阻塞生产者和消费者了。
由于没有共享缓冲区,因此无需阻塞。由于每个消费者都是一个Objective-C块,通过Objective-C块上下文捕获机制捕获生成的数据,因此消费者与数据之间存在一对一的关系。

1
哇,这太棒了。我得好好考虑一下。谢谢! - Rhythmic Fistman
2
好的,现在我明白了。这只是一种简化事情的心态转变。虽然我可以做几乎所有以前能做的事情,但不确定关于不透明队列,可能无法取消未完成的消费者任务,还是有点担心的。我会在新代码中使用它,但对于我所考虑的问题,我不能使用它,因为我的消费者已经在一个真实的线程上安排好了。谢谢你的精彩回答,可惜你没有写文档。 - Rhythmic Fistman
我不喜欢这个解决方案的原因是生产者决定消费者如何消费数据(必须指定块)。而我可能希望消费者能够根据处理步骤在代码中的不同位置消费数据。 - user102008
@user102008 你可以在块内包含对其他数据的引用(或使其可访问),或者使用不同种类的块(也许是某种块工厂)? - Richard
@user102008,该块可以简单地调用[SomeConsumerClass consumeData:dataProducedByTheProducer]或类似的内容。这样可以将消费者对数据的处理细节抽象出来。 - Ken Thomases
1
不需要指定生产和消费的内容很有趣,但让边界成为系统负载是一种反模式。苹果公司表示您可以使用GCD简化生产者-消费者代码,但这种“简化”的代价是放弃您明确定义的限制(即缓冲区大小)。难怪代码更短了。他们在这种情况下的建议过于简单化了。但是这个例子能否被拯救?可以添加一个限制吗? - Rhythmic Fistman

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