【发布时间】:2012-12-10 22:19:24
【问题描述】:
我正在使用Go语言进行数据导入工作,我想将每个步骤编写为闭包,并使用通道进行通信,即每个步骤都是并发的。问题可以通过以下结构来定义。
- 从数据源获取Widgets
- 将源 1 中的翻译添加到 Widgets。
- 将源 2 中的翻译添加到 Widgets。
- 将来源 1 的定价添加到 小部件。
- 将 WidgetRevisions 添加到 Widgets。
- 将源 1 中的翻译添加到 WidgetRevisions
- 将源 2 中的翻译添加到 WidgetRevisions
就这个问题而言,我只处理前三个步骤,这些步骤必须在新的 Widget 上执行。我假设在此基础上,第四步可以作为一个管道步骤来实现,它本身是根据一个子三步管道来实现的,以控制 *WidgetRevision*s
为此,我一直在编写一小段代码来提供以下 API:
// A Pipeline is just a list of closures, and a smart
// function to set them all off, keeping channels of
// communication between them.
p, e, d := NewPipeline()
// Add the three steps of the process
p.Add(whizWidgets)
p.Add(popWidgets)
p.Add(bangWidgets)
// Start putting things on the channel, kick off
// the pipeline, and drain the output channel
// (probably to disk, or a database somewhere)
go emit(e)
p.Execute()
drain(d)
我已经实现了它(代码在 Gist 或 Go Playground),但它以 100% 成功 失败率陷入僵局
调用p.Execute() 时会出现死锁,因为可能其中一个通道最终无事可做,没有任何事情可以发送,也没有工作可做......
向emit() 和drain() 添加几行调试输出,我看到以下输出,我相信闭包调用之间的流水线是正确的,并且我看到了一些小部件 被省略。
Emitting A Widget
Input Will Be Emitted On 0x420fdc80
Emitting A Widget
Emitting A Widget
Emitting A Widget
Output Will Drain From 0x420fdcd0
Pipeline reading from 0x420fdc80 writing to 0x420fdd20
Pipeline reading from 0x420fdd20 writing to 0x420fddc0
Pipeline reading from 0x420fddc0 writing to 0x42157000
以下是我对这种方法的一些了解:
- 我相信这种设计“饿死”一个或另一个协程的情况并不少见,我相信这就是死锁的原因
- 如果管道一开始就有东西送入,我更愿意(API 将实现
Pipeline.Process(*Widget)- 如果我能做到这一点,排水管可能是一个“步骤”,只是没有将任何东西传递给下一个函数,这可能是一个更干净的 API
- 我知道我没有实现任何类型的梯级缓冲区,所以我完全有可能只是让机器的可用内存过载
- 我真的不相信这是好的 Go 风格......但它似乎利用了很多 Go 功能,但这并不是真正的好处
- 因为 WidgetRevisions 也需要管道,所以我想让我的管道更通用,也许
interface{}类型是解决方案,我不知道 Go 是否足够好以确定这是否明智或还没有。 - 有人建议我考虑实施互斥锁以防止出现竞争条件,但我相信我会省心,因为每个闭包都会在 Widget 结构的一个特定单元上运行,但是我很乐意接受教育关于那个话题。
总结:我该如何修复这个代码,应该我修复这个代码,如果你是一个比我更有经验的 Go 程序员,你会如何解决这个问题“顺序工作单元”问题?
【问题讨论】:
-
我已经解决了死锁问题,问题有两个,
case i == len(pipe.processes)错了,len()总是+1(菜鸟错误)。 -
我也将该条件实现为
in, out = out, pipe.Drain,而不是制作一个新管道,这很愚蠢......但是关于这是一个体面的设计立场的问题。我觉得它有可能在我面前爆炸,并且类型不可重用,尽管我对流水线机制非常满意...... -
最终的代码在play.golang.org/p/YXYLFR3qcz,但它迫切需要变得更通用。
-
线索是,在每次运行中,
Output Will Drain From 0x420fdcd0行具有相同的值(包括地址),但该地址不会在管道 i/o 输出中重复。这让我查看了Execute()函数的 end case,在那里我看到了我的错误。 -
顺便说一句,使用
var x = func()...有点像舞台表演。只需使用func x()。
标签: concurrency go deadlock pipeline