在Golang函数中实例化goroutine的方法

在Golang函数中实例化goroutine的方法 你好,

我有一个需求,在函数中编写一个goroutine会更方便(即,它不是在主函数中创建的)。一旦函数退出,goroutine是否会继续按预期运行?这被认为是好的做法吗?到目前为止,我看到的唯一示例都只展示了在主线程/例程中实例化goroutine,我不确定这样做是强制要求,还是仅仅为了示例简洁。

一个看似有效的示例来演示:

package main

import (
	"fmt"
	"time"
)

func printForever() { // 我们的goroutine
	x := 0
	for {
		fmt.Printf("hello again # %v\n", x)
		x++
		time.Sleep(1 * time.Second)
	}
}

func doSomething() {
	go printForever()
	fmt.Println("all done here")
}

func main() {
	doSomething() // 调用我的例程
	for {
	} // 永远停留在这里
}

更多关于在Golang函数中实例化goroutine的方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

为什么 你启动新的 goroutine,或者 在那些 goroutine 中做什么,可能被认为是好的实践,也可能不是,但从 main 函数以外的函数启动 goroutine 本身没有任何问题或不寻常之处;这完全是正常的。

更多关于在Golang函数中实例化goroutine的方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


为了澄清一下,让一个 goroutine 并发运行是可以的,即使调用并创建它的函数已经退出。对吗?我的示例程序就是这样工作的,目前一切正常…

感谢 @skillian,你的动作太快了,哈哈。我刚上传了一个我快速拼凑出来的可行示例。它执行得很好,但我不确定这种惯用风格的代码在 Go 中是否不受欢迎。我想你已经回答了我的问题,谢谢。

对于我的特定应用场景,是的,我希望这个 goroutine 能够无限期地运行。我之前曾使用布尔通道来实现优雅退出。我发现,直接退出主循环(例如按 CTRL+C 或退出一个用 Go 编写的 GUI 程序)会突然停止 Go 协程,这是可以预料的。利用通道在 goroutine 内部触发事件来运行 defer 代码等,效果很好。

我之前不知道 context.Context 这个选项,谢谢。我需要多读一些相关资料。

在创建它的函数返回后,让一个 goroutine 继续运行是可以的。goroutine 与实例化它的函数之间没有任何关联,反之亦然。

我怀疑你在主函数中有一个 for {} 循环,它什么都不做,只是为了确保你的 goroutine 在有机会运行之前不会被停止。我要说的是,就你目前的程序而言,你的 printForever 程序确实会在整个程序运行期间一直执行。有些情况下你确实希望如此,也有些情况下你希望它在后台运行,独立于启动它的函数,但仍然希望在某个时刻能够取消它。如果是这种情况,你可以使用 context.Context 来发出信号,指示何时停止在后台运行的 goroutine。

func main() {
    fmt.Println("hello world")
}

是的,在函数中启动goroutine是完全有效的,并且goroutine会继续运行,即使创建它的函数已经退出。只要主goroutine(或任何其他goroutine)仍在运行,程序就不会退出,所有已启动的goroutine都会继续执行。

在你的示例中,doSomething函数启动了一个goroutine,然后立即返回。由于主函数中有一个无限循环,程序不会退出,因此printForever goroutine会继续运行。这是一个常见的模式。

以下是一个更实际的示例,演示了在函数中启动goroutine,并使用通道(channel)进行通信和控制:

package main

import (
	"fmt"
	"time"
)

// worker是一个在后台运行的goroutine,它从jobs通道接收工作,并将结果发送到results通道。
func worker(id int, jobs <-chan int, results chan<- int) {
	for j := range jobs {
		fmt.Printf("worker %d started job %d\n", id, j)
		time.Sleep(time.Second) // 模拟工作耗时
		fmt.Printf("worker %d finished job %d\n", id, j)
		results <- j * 2
	}
}

// startWorkers是一个函数,它在内部启动多个goroutine(worker池)。
func startWorkers(numWorkers int, jobs <-chan int, results chan<- int) {
	for w := 1; w <= numWorkers; w++ {
		go worker(w, jobs, results)
	}
}

func main() {
	const numJobs = 5
	jobs := make(chan int, numJobs)
	results := make(chan int, numJobs)

	// 在函数中启动worker池
	startWorkers(3, jobs, results)

	// 发送工作
	for j := 1; j <= numJobs; j++ {
		jobs <- j
	}
	close(jobs)

	// 收集结果
	for a := 1; a <= numJobs; a++ {
		<-results
	}
}

在这个示例中,startWorkers函数启动了3个worker goroutine。即使startWorkers函数已经返回,这些worker goroutine仍然继续运行,处理从jobs通道接收的工作,直到通道被关闭。这是一种常见的并发模式,用于构建worker池。

因此,在函数中启动goroutine不仅是允许的,而且是Go语言中组织并发代码的推荐方式。关键是要确保你管理好goroutine的生命周期,避免goroutine泄漏(例如,使用上下文(context)取消、通道关闭或等待组(sync.WaitGroup)来协调goroutine的退出)。

回到顶部