如何进行平行通用治疗?

时间:2017-01-23 09:24:04

标签: go goroutine

我有以下功能:

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例程之间的共享索引。

3 个答案:

答案 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(&current, 1)-1
                if Loadint32(&current) > Loadint32(&lastone) {
                    break
                } 
                dotreatment(entries[idx])
            }
            wg.Done()
        }()
    }
    wg.Wait()
}