为什么在使用goroutine后,计算任务的总时间基本相同?

huangapple go评论92阅读模式
英文:

Why the total time of computing tasks after using goroutine is basically the same

问题

当不使用goroutine时,执行500 * 100000000次加一的操作需要1m12.2857724秒。代码如下:

// 1m12.2857724s
start := time.Now()
for i := 0; i < 500; i++ {
    res := 0
    for j := 0; j < 100000000; j++ {
        res++
    }
}
duration := time.Since(start)
fmt.Println(duration)

当使用goroutine时,10个goroutine执行50 * 100000000次加一的操作需要1m12.0174541秒。代码如下:

// 1m12.0174541s
start := time.Now()
ch := make(chan bool)
for i := 0; i < 10; i++ {
    go func(ch chan bool) {
        for i := 0; i < 50; i++ {
            res := 0
            for j := 0; j < 100000000; j++ {
                res++
            }
        }
        ch <- true
    }(ch)
    <-ch
}

duration := time.Since(start)
fmt.Println(duration)

为什么使用goroutine不会节省时间呢?

英文:

When not using goroutine,500 * 100000000 times plus one

// 1m12.2857724s
	start := time.Now()
	for i := 0; i &lt; 500; i++ {
		res := 0
		for j := 0; j &lt; 100000000; j++ {
			res++
		}
	}
	duration := time.Since(start)
	fmt.Println(duration)

When using goroutine, 10 goroutines execute 50 * 100000000 times plus one

// 1m12.0174541s
	start := time.Now()
	ch := make(chan bool)
	for i := 0; i &lt; 10; i++ {
		go func(ch chan bool) {
			for i := 0; i &lt; 50; i++ {
				res := 0
				for j := 0; j &lt; 100000000; j++ {
					res++
				}
			}
			ch &lt;- true
		}(ch)
		&lt;- ch
	}

	duration := time.Since(start)
	fmt.Println(duration)

Why use goroutine does not save time

答案1

得分: 3

ch通道是无缓冲的。你在最后启动一个goroutine并在通道上发送一个值,然后在启动另一个goroutine之前立即从通道接收。这是一个阻塞操作。在一个goroutine完成之前,你不会启动新的goroutine。与第一个解决方案相比,你不会获得任何好处。

一个"解决方案"是将通道改为带缓冲,并且只有在所有goroutine都已启动后才开始从中接收:

ch := make(chan bool, 10)
for i := 0; i < 10; i++ {
	go func(ch chan bool) {
		for i := 0; i < 50; i++ {
			res := 0
			for j := 0; j < 100000000; j++ {
				res++
			}
		}
		ch <- true
	}(ch)
}

for i := 0; i < 10; i++ {
	<-ch
}

这将在我的计算机上(4个CPU核心)实现近4倍的加速

等待所有goroutine的更好、更符合惯例的方法是使用sync.WaitGroup

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < 50; i++ {
			res := 0
			for j := 0; j < 100000000; j++ {
				res++
			}
		}
	}()
}
wg.Wait()

还要注意,只有当goroutine执行的任务是"重要的"时,使用多个goroutine才值得,参见:

https://stackoverflow.com/questions/52403157/matrix-multiplication-with-goroutine-drops-performance/52403366#52403366

https://stackoverflow.com/questions/50179290/vectorise-a-function-taking-advantage-of-concurrency/50179617#50179617

英文:

The ch channel is unbuffered. You launch a goroutine and send a value on the channel at the end, and right after that, before launching another goroutine you receive from it. This is a blocking operation. You won't start a new goroutine until one is finished. You gain nothing compared to the first solution.

One "solution" is to make the channel buffered, and only start receiving from it once all goroutines have been launched:

ch := make(chan bool, 10)
for i := 0; i &lt; 10; i++ {
	go func(ch chan bool) {
		for i := 0; i &lt; 50; i++ {
			res := 0
			for j := 0; j &lt; 100000000; j++ {
				res++
			}
		}
		ch &lt;- true
	}(ch)
}

for i := 0; i &lt; 10; i++ {
	&lt;-ch
}

This will result in almost 4x speedup on my computer (4 CPU cores).

A better, more idiomatic way to wait for all goroutines is to use sync.WaitGroup:

var wg sync.WaitGroup
for i := 0; i &lt; 10; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i &lt; 50; i++ {
			res := 0
			for j := 0; j &lt; 100000000; j++ {
				res++
			}
		}
	}()
}
wg.Wait()

Also note that using multiple goroutines is only worth it if the task they do is "significant", see:

https://stackoverflow.com/questions/52403157/matrix-multiplication-with-goroutine-drops-performance/52403366#52403366

https://stackoverflow.com/questions/50179290/vectorise-a-function-taking-advantage-of-concurrency/50179617#50179617

答案2

得分: 0

你可以以一种简单的方式有效地启动10个goroutine,并在它们完成工作后从每个goroutine接收结果。

检查你的代码和下面这段代码的CPU使用情况:

func add(ch chan int) {
    r := 0
    for i := 0; i < 50; i++ {
        for j := 0; j < 100000000; j++ {
            r++
        }
    }
    ch <- r
}

func main() {
    start := time.Now()
    ch := make(chan int)
    res := 0
    for i := 0; i < 10; i++ {
        go add(ch)
    }
    for i := 0; i < 10; i++ {
        res += <-ch
    }
    duration := time.Since(start)
    fmt.Println(duration, res)
}

输出结果为:

3.747405005s 50000000000

这将成功启动10个goroutine,每个goroutine执行它们的工作,并在完成后通过通道返回结果。

英文:

You could effectively launch the 10 goroutines, and receive from each after they complete their work, in a simple way.

Check the CPU usage of your code, and this one

func add(ch chan int) {
	r := 0
	for i := 0; i &lt; 50; i++ {
		for j := 0; j &lt; 100000000; j++ {
			r++
		}
	}
	ch &lt;- r
}

func main() {
	start := time.Now()
	ch := make(chan int)
	res := 0
	for i := 0; i &lt; 10; i++ {
		go add(ch)
	}
	for i := 0; i &lt; 10; i++ {
		res += &lt;-ch
	}
	duration := time.Since(start)
	fmt.Println(duration, res)
}

Output

> 3.747405005s 50000000000

That will successfully launch the 10 goroutines, each of them perform their work and return the results down the channel once they're done.

huangapple
  • 本文由 发表于 2021年12月20日 18:09:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/70420229.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定