For loop 环路并联

For loop 环路并联,for-loop,concurrency,go,For Loop,Concurrency,Go,我希望for循环使用go例程是并行的。我试着使用频道,但没用。我的主要问题是,我希望在继续之前等待所有迭代完成。这就是为什么在不起作用之前简单地编写go。我尝试使用频道(我认为是错误的方式),但这使我的代码更慢 func createPopulation(populationSize int, individualSize int) []Individual { population := make([]Individual, populationSize) //i want

我希望for循环使用go例程是并行的。我试着使用频道,但没用。我的主要问题是,我希望在继续之前等待所有迭代完成。这就是为什么在不起作用之前简单地编写
go
。我尝试使用频道(我认为是错误的方式),但这使我的代码更慢

func createPopulation(populationSize int, individualSize int) []Individual {
    population := make([]Individual, populationSize)

    //i want this loop to be work parallel
    for i := 0; i < len(population); i++ {
        population[i] = createIndividual(individualSize)
    }

    return population
}

func createIndividual(size int) Individual {
    var individual = Individual{make([]bool, size), 0}

    for i := 0; i < len(individual.gene); i++ {
        if rand.Intn(2)%2 == 1 {
            individual.gene[i] = true
        } else {
            individual.gene[i] = false
        }
    }

    return individual
}

因此,goroutine基本上不应该返回一个值,而是将其向下推到一个通道中。如果要等待所有goroutine完成,只需计算goroutine的数量,或使用WaitGroup。在这个例子中,这是一个过度的杀伤力,因为大小是已知的,但无论如何,这是一个很好的实践。下面是一个经过修改的示例:

package main

import (
    "math/rand"
    "sync"
)

type Individual struct {
    gene    []bool
    fitness int
}


func createPopulation(populationSize int, individualSize int) []Individual  {

    // we create a slice with a capacity of populationSize but 0 size
    // so we'll avoid extra unneeded allocations
    population := make([]Individual, 0, populationSize)

    // we create a buffered channel so writing to it won't block while we wait for the waitgroup to finish
    ch := make(chan Individual, populationSize)

    // we create a waitgroup - basically block until N tasks say they are done
    wg := sync.WaitGroup{}

    for i := 0; i < populationSize; i++ {

        //we add 1 to the wait group - each worker will decrease it back
        wg.Add(1)

        //now we spawn a goroutine
        go createIndividual(individualSize, ch, &wg)
    }

    // now we wait for everyone to finish - again, not a must.
    // you can just receive from the channel N times, and use a timeout or something for safety
    wg.Wait()

    // we need to close the channel or the following loop will get stuck
    close(ch)

    // we iterate over the closed channel and receive all data from it
    for individual := range ch {

        population = append(population, individual)
    }
    return population

}   

func createIndividual(size int, ch chan Individual, wg *sync.WaitGroup) {

    var individual = Individual{make([]bool, size), 0}

    for i := 0; i < len(individual.gene); i++ {
        if rand.Intn(2)%2 == 1 {
            individual.gene[i] = true
        } else {
            individual.gene[i] = false
        }
    }

    // push the population object down the channel
    ch <- individual
    // let the wait group know we finished
    wg.Done()

}
主程序包
进口(
“数学/兰德”
“同步”
)
类型单个结构{
基因[]布尔
适应度整数
}
func createPopulation(populationSize int,individualSize int)[个人{
//我们创建了一个容量为populationSize但大小为0的切片
//因此,我们将避免额外的不必要的分配
填充:=生成([]个,0,填充大小)
//我们创建了一个缓冲通道,这样在等待waitgroup完成时,对它的写入就不会被阻塞
ch:=制造(成龙个人,人口规模)
//我们创建一个waitgroup-基本上是阻塞,直到N个任务说它们已经完成
wg:=sync.WaitGroup{}
对于i:=0;ich由于您事先知道您将拥有多少个人,我将避免使用频道,只在goroutine
createIndividual
中分配
population
的个人成员。
createIndividual
的签名如下所示:

type Individual struct {
    gene []bool
    fitness int
}
func createIndividual(wg *sync.WaitGroup, individual *Individual, size int) 
population := make([]Individual, populationSize)
wg := &sync.WaitGroup{}
wg.Add(len(population))

for i := 0; i < len(population); i++ {
    go createIndividual(wg, &population[i], individualSize)
}

wg.Wait()
ch := make(chan *Individual)
for i := 0; i < nworkers; i++ {
    go initIndividuals(individualSize, ch)
}

population := make([]Individual, populationSize)
for i := 0; i < len(population); i++ {
    ch <- &population[i]
}
close(ch)
func initIndividuals(size int, ch <-chan *Individual) {
    for individual := range ch {
        // Or alternatively inline the createIndividual() code here if it is the only call
        *individual = createIndividual(size)
    }
}
调用代码如下所示:

type Individual struct {
    gene []bool
    fitness int
}
func createIndividual(wg *sync.WaitGroup, individual *Individual, size int) 
population := make([]Individual, populationSize)
wg := &sync.WaitGroup{}
wg.Add(len(population))

for i := 0; i < len(population); i++ {
    go createIndividual(wg, &population[i], individualSize)
}

wg.Wait()
ch := make(chan *Individual)
for i := 0; i < nworkers; i++ {
    go initIndividuals(individualSize, ch)
}

population := make([]Individual, populationSize)
for i := 0; i < len(population); i++ {
    ch <- &population[i]
}
close(ch)
func initIndividuals(size int, ch <-chan *Individual) {
    for individual := range ch {
        // Or alternatively inline the createIndividual() code here if it is the only call
        *individual = createIndividual(size)
    }
}

您可以看到一个完整的代码示例。

向这样的循环添加受控并行性的一种常见方法是生成多个worker Goroutine,这些worker Goroutine将从通道中读取任务。该函数可能有助于确定生成多少worker(请确保适当设置以利用这些CPU)。然后,您只需将作业写入频道,它们将由工人处理

在这种情况下,任务是初始化总体切片的元素,因此使用
*单个
指针的通道可能有意义。类似如下:

type Individual struct {
    gene []bool
    fitness int
}
func createIndividual(wg *sync.WaitGroup, individual *Individual, size int) 
population := make([]Individual, populationSize)
wg := &sync.WaitGroup{}
wg.Add(len(population))

for i := 0; i < len(population); i++ {
    go createIndividual(wg, &population[i], individualSize)
}

wg.Wait()
ch := make(chan *Individual)
for i := 0; i < nworkers; i++ {
    go initIndividuals(individualSize, ch)
}

population := make([]Individual, populationSize)
for i := 0; i < len(population); i++ {
    ch <- &population[i]
}
close(ch)
func initIndividuals(size int, ch <-chan *Individual) {
    for individual := range ch {
        // Or alternatively inline the createIndividual() code here if it is the only call
        *individual = createIndividual(size)
    }
}

initIndividuals
函数也被修改为使用附加参数,并添加
defer wg.Done()
作为第一条语句。现在调用
wg.Wait()
将一直阻止,直到所有工作进程都完成。然后,您可以返回完全构造的
填充片。

对于您的特定问题,您根本不需要使用通道

但是,除非您的
createIndividual
花费一些时间进行计算,否则在并行运行时,goroutines之间的上下文切换总是要慢得多

type Individual struct {
    gene    []bool
    fitness int
}

func createPopulation(populationSize int, individualSize int) (population []*Individual) {
    var wg sync.WaitGroup
    population = make([]*Individual, populationSize)

    wg.Add(populationSize)
    for i := 0; i < populationSize; i++ {
        go func(i int) {
            population[i] = createIndividual(individualSize)
            wg.Done()
        }(i)
    }
    wg.Wait()
    return
}

func createIndividual(size int) *Individual {
    individual := &Individual{make([]bool, size), 0}

    for i := 0; i < size; i++ {
        individual.gene[i] = rand.Intn(2)%2 == 1
    }

    return individual
}

func main() {
    numcpu := flag.Int("cpu", runtime.NumCPU(), "")
    flag.Parse()
    runtime.GOMAXPROCS(*numcpu)
    pop := createPopulation(1e2, 21e3)
    fmt.Println(len(pop))
}

如果您希望避免将并发逻辑与业务逻辑混合,我编写了这个库来帮助您实现这一点。它封装了并发逻辑,因此您不必担心它

在你的例子中:

package main

import (
    "github.com/shomali11/parallelizer"
    "fmt"
)

func main() {
    populationSize := 100
    results = make([]*Individual, populationSize)

    options := &Options{ Timeout: time.Second }
    group := parallelizer.NewGroup(options)
    for i := 0; i < populationSize; i++ {
        group.Add(func(index int, results *[]*Individual) {
            return func () {
                ...

                results[index] = &Individual{...}
            }
        }(i, &results))
    }

    err := group.Run()

    fmt.Println("Done")
    fmt.Println(fmt.Sprintf("Results: %v", results))
    fmt.Printf("Error: %v", err) // nil if it completed, err if timed out
}
主程序包
进口(
“github.com/shomali11/parallelizer”
“fmt”
)
func main(){
人口规模:=100
结果=制造([]*个人,人口规模)
选项:=&options{Timeout:time.Second}
组:=parallelizer.NewGroup(选项)
对于i:=0;i
回答得很好,但我有一种感觉,
population:=make([]Individual,populationSize)
可能应该是
population:=make([]Individual,0)
在本例中,否则,
append
语句会将新个体放在长度为
populationSize
@Intermernet的空片段的末尾。你说得对,我错过了。我会修正我的答案。修正为分配一个已知容量但只有0个成员的片段。这似乎可行,但在四核上不会更快(事实上:速度较慢)。你能告诉我为什么吗?@PhilippSander是的。1.你需要调用
runtime.GOMAXPROC()
,以获得所需的CPU数量。2.通道和生成goroutine的开销很大。除非每个goroutine的任务都长时间运行,否则你不会看到任何收益。这肯定会起作用,但有人可能会认为这与Go的做法背道而驰“通过交流共享内存”这句成语。@不是高尔夫球手,是的,但由于这似乎是一个非常小且孤立的问题,我认为为了性能和可读性,这样做是可以的。当然,这一切都取决于
createIndividual
最终将做什么。