Golang中频繁启动Goroutine会对机器造成性能负担吗

Golang中频繁启动Goroutine会对机器造成性能负担吗 你好

你是否为不同的"数据库查询"使用 Goroutine? 我在"数据库查询"上尝试过,我的意思是一个 Goroutine 用于"更新",一个用于"插入"等等… 这比"简单函数"要慢 那么在下面的代码中,为什么"简单函数"比"Goroutines 函数"更快?

这里是 playGround:

Go Playground - The Go Programming Language

代码审查:

package main

import (
	p "fmt"
	w "sync"
	t "time"
)

var wg w.WaitGroup
func main() {
	simple(200)
	goRoutines(200)
}

简单函数:

func simple(n int) {
	startTime := t.Now().UnixNano()
	for i:=0;i< n;i++ {
		 gen(10, 50, 100, 200)
	}

	p.Println("simple     : ",t.Now().UnixNano()-startTime)
}

func gen(n ...int) {
	var slice []int
	for _, k := range n {
			slice = append(slice, k*10+50/2)
		}
}

Goroutines 函数:

func goRoutines(n int)  {
	startTime := t.Now().UnixNano()
	wg.Add(n)

	for i:=0;i< n;i++ {
		go gen2(10, 50, 100, 200)
	}

	wg.Wait()
	p.Println("goRoutines : ",t.Now().UnixNano()-startTime)
}

func gen2(n ...int) {
	var slice []int
	for _, k := range n {
		slice = append(slice, k*10+50/2)
	}
	wg.Done()
}

结果:

simple : 49033794
goRoutines : 65791173

提前感谢。


更多关于Golang中频繁启动Goroutine会对机器造成性能负担吗的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

非常感谢

更多关于Golang中频繁启动Goroutine会对机器造成性能负担吗的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢,Johan
链接已失效(在此阅读更多内容

奇怪。点击这里的链接不起作用,但我在谷歌上搜索后却能进入该页面,而且地址仍然相同 😕

试试这个:https://www.google.se/search?q=Why+goroutines+are+not+lightweight+threads

这里还有一些链接:http://www.golangbootcamp.com/book/concurrency 特别是Rob Pike关于"并发不是并行"的演讲非常有趣。

你的 Go 协程版本运行较慢的原因是:协程会一直运行直到遇到阻塞调用,比如等待磁盘、网络等操作。因此,在紧密循环中运行协程不会给其他协程分配时间,除非你拥有多个核心,这样它们才能并行执行。

更多信息请阅读:https://codeburst.io/why-goroutines-are-not-lightweight-threads-7c460c1f155

不要中断!

Go 运行时调度器采用协作式调度,这意味着只有当当前协程阻塞或完成时,才会调度另一个协程。以下是一些情况:

  • 通道的发送和接收操作(如果这些操作会阻塞)
  • Go 语句(尽管不能保证新协程会立即被调度)
  • 阻塞性系统调用(如文件和网络操作)
  • 在垃圾回收周期停止后

在Golang中,频繁启动Goroutine本身不会直接对机器造成显著的性能负担,因为Goroutine是轻量级的,由Go运行时管理。然而,在你的代码中,"简单函数"比"Goroutines函数"更快的原因在于任务的性质和Goroutine的开销。

你的gengen2函数执行的是非常简单的计算(仅涉及整数运算和切片追加),这种操作在单个Goroutine中顺序执行时,CPU可以高效处理,几乎没有上下文切换的开销。而使用Goroutines时,尽管它们是轻量的,但每个Goroutine的创建、调度和同步(通过WaitGroup)都会引入额外的开销,包括:

  • Goroutine的初始化和调度成本。
  • WaitGroup的原子操作(如AddDoneWait)导致的锁竞争和内存屏障。
  • 在多个Goroutines间切换的上下文开销。

对于这种计算密集型但简单的任务,Goroutines的并行优势无法抵消这些开销,因此顺序执行更快。只有当任务涉及I/O操作(如数据库查询、网络请求)或复杂计算时,Goroutines才能通过并发提高性能,因为它们允许在等待时执行其他任务。

在你的代码中,gen2函数没有实际I/O,只是纯计算,因此Goroutines版本更慢。以下是代码示例,展示了一个更平衡的用例,其中Goroutines可能更快(例如,模拟I/O延迟):

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func main() {
    simple(200)
    goRoutines(200)
}

func simple(n int) {
    startTime := time.Now().UnixNano()
    for i := 0; i < n; i++ {
        gen(10, 50, 100, 200)
    }
    fmt.Println("simple     : ", time.Now().UnixNano()-startTime)
}

func gen(n ...int) {
    var slice []int
    for _, k := range n {
        slice = append(slice, k*10+50/2)
    }
    // 模拟轻微延迟,以接近真实场景
    time.Sleep(1 * time.Microsecond)
}

func goRoutines(n int) {
    startTime := time.Now().UnixNano()
    wg.Add(n)
    for i := 0; i < n; i++ {
        go gen2(10, 50, 100, 200)
    }
    wg.Wait()
    fmt.Println("goRoutines : ", time.Now().UnixNano()-startTime)
}

func gen2(n ...int) {
    var slice []int
    for _, k := range n {
        slice = append(slice, k*10+50/2)
    }
    // 模拟轻微延迟
    time.Sleep(1 * time.Microsecond)
    wg.Done()
}

在这个修改版本中,如果添加了time.Sleep模拟I/O延迟,Goroutines版本可能更快,因为它能并发处理等待。但在原代码中,由于任务太简单,顺序执行总是更高效。总结:Goroutines适用于I/O密集型或可并行化的重型计算任务,而非简单循环计算。

回到顶部