For loop 环路并联
我希望for循环使用go例程是并行的。我试着使用频道,但没用。我的主要问题是,我希望在继续之前等待所有迭代完成。这就是为什么在不起作用之前简单地编写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
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;i ch由于您事先知道您将拥有多少个人,我将避免使用频道,只在goroutinecreateIndividual
中分配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
最终将做什么。