我有以下功能:
func myrun(entries []WhatEverType) {
for i := range entries {
dotreatment(entries[i])
}
}
我想对并行进行并行调用,我尝试了以下内容:
func myrunMT(entries []WhatEverType) {
var wg sync.WaitGroup
stopped := false
threads := 5 //number of threads could be argument
com := make(chan WhatEverType, 100) //size of chan could be argument
wg.Add(threads)
for i := 0; i < threads; i++ {
go func() {
for !stopped || len(com) {
select {
case entry := <-com:
dotreatment(entry) //lock if necessary
case time.After(100*time.Millisecond):
}
}
wg.Done()
}()
}
for _, entry := range entries {
com <- entry
}
stopped = true
wg.Wait()
}
有没有更好的方法呢?特别是我想避免通过陈发送所有条目,只使用go例程之间的共享索引。
答案 0 :(得分:1)
首先,您的解决方案有数据竞争。您正在从多个goroutine中读取和修改stopped
变量。
一个简单的解决方案可能是划分传递切片的索引范围,并有多个goroutines处理不同的索引范围。这就是它的样子:
func process(ms []My) {
workers := 5
count := len(ms) / workers
if count*workers < len(ms) {
count++
}
wg := &sync.WaitGroup{}
for idx := 0; idx < len(ms); {
wg.Add(1)
idx2 := idx + count
if idx2 > len(ms) {
idx2 = len(ms)
}
ms2 := ms[idx:idx2]
idx = idx2
go func() {
defer wg.Done()
for i := range ms2 {
handle(&ms2[i])
}
}()
}
wg.Wait()
}
func handle(m *My) {}
对于可以使用runtime.GOMAXPROCS()
的工作器goroutine的数量,就像处理条目不涉及IO操作(或等待goroutine之外的东西)一样,不需要让Go运行时管理比goroutine更多的goroutine。那些可以积极运作的人:
workers := runtime.GOMAXPROCS(0)
请注意,尽管此解决方案不涉及通过通道发送条目,但如果一个(某些)goroutine提前完成,CPU利用率可能会在最后降低(当较少的goroutine有工作要做时)。
生产者 - 消费者模型的优势在于,所有工人goroutine都将平等地工作到最后。但是,通信开销可能不可忽略。一个是否优于另一个取决于每个条目需要完成的工作量。
改进的版本可以混合2:你可以在一个频道上发送较小的片段,较小的索引范围,例如批量100个条目。与第一个解决方案相比,这可以减少空闲时间,并且还可以减少通信开销,因为条目是通过信道单独发送的,因此发送的值只是总数的百分之一。
这是这个改进的混合版本的示例实现:
func process(ms []My) {
workers := runtime.GOMAXPROCS(0)
// 100 jobs per worker average:
count := len(ms) / workers / 100
if count < 1 {
count = 1
}
ch := make(chan []My, workers*2) // Buffer size scales with # of workers
wg := &sync.WaitGroup{}
// Start workers
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
defer wg.Done()
for ms2 := range ch {
for j := range ms2 {
handle(&ms2[j])
}
}
}()
}
// Send jobs:
for idx := 0; idx < len(ms); {
idx2 := idx + count
if idx2 > len(ms) {
idx2 = len(ms)
}
ch <- ms[idx:idx2]
idx = idx2
}
// Jobs sent, close channel:
close(ch)
// Wait workers to finish processing all jobs:
wg.Wait()
}
请注意,没有stopping
变量来表示完成。相反,我们在每个goroutine的通道上使用for range
,因为它在通道上的范围,直到通道关闭,并且它可以安全地用于并发使用。一旦通道关闭和,goroutines处理了在通道上发送的所有作业,它们就会终止,整个处理算法也会终止(而不是更早 - 意味着所有作业都将被处理)。
答案 1 :(得分:0)
我不会混合频道和同步原语。仅使用频道是惯用的Go。请记住,Go例程不是线程,更轻,开销更低。启动其中的一百万并不是什么大问题。
如果结果的顺序无关紧要,我会做这样的事情:
func parallelRun(input []WhateverInputType) []WhateverOutputType {
out := make(chan WhateverOutputType, len(input))
for _, item := range input {
go func(i WhateverInputType) {
out <- process(i)
}(item)
}
res := make([]WhateverOutputType, len(input))
for i := 0; i < len(input); i++ {
res[i] = <-out
}
return res
}
func process(input WhateverInputType) WhateverOutputType {
time.Sleep(50 * time.Millisecond)
return WhateverOutputType{}
}
假设'进程'比收集结果需要更长的时间,我甚至会使用阻塞通道out := make(chan WhateverOutputType)
请注意,将数组作为参数传递并不理想(有复制)但我试过保持原始代码的精神。
答案 2 :(得分:0)
搜索后,我得到以下内容,没有使用共享索引的数据副本:
func myrunMT(entries []WhatEverType) int {
lastone := int32(len(entries)-1)
current := int32(0)
var wg sync.WaitGroup
threads := 5
//start threads
wg.Add(threads)
for i := 0; i < threads; i++ {
go func() {
for {
idx := atomic.AddInt32(¤t, 1)-1
if Loadint32(¤t) > Loadint32(&lastone) {
break
}
dotreatment(entries[idx])
}
wg.Done()
}()
}
wg.Wait()
}