Golang Go语言中一个 goroutine 相关的问题
package main
import (
“fmt”
“io”
“net/http”
)
func fetch(url string) string {
resp, err := http.Get(url)
if err != nil {
return err.Error()
}
defer resp.Body.Close()
written, err := io.Copy(io.Discard, resp.Body)
if err != nil {
return err.Error()
}
result := fmt.Sprintf("%s %s %d", url, resp.Status, written)
return result
}
func request() string {
ch := make(chan string)
go func() {
ch <- fetch(“http://www.163.com”)
}()
go func() {
ch <- fetch(“http://www.sohu.com”)
}()
go func() {
ch <- fetch(“http://www.sina.com”)
}()
result := <-ch
return result
}
func main() {
fmt.Println(request())
fmt.Scanln()
}
这里假设 163.com goroutine 总是第一个执行完,此后 request 函数执行返回。 此时,进程未退出,另外两个 goroutine 仍将把 fetch 的结果发送到 ch ,但是由于 ch 是无缓存的,同时又因为 request 已经返回, 无人从 ch 中接收数据,所以另两个 goroutine 应该会死锁,一直无法退出才是。
但是实际执行时错不报错,这是为什么?多谢
Golang Go语言中一个 goroutine 相关的问题
更多关于Golang Go语言中一个 goroutine 相关的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
先不吐槽标题了。
你这个问题很简单,因为卡死的不是“主线程”。
main 能不能 exit 和其他线程又没关系,只要主线程退出就行,操作系统会负责给你擦屁股的。
更多关于Golang Go语言中一个 goroutine 相关的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在 go 的角度,只有这样的代码才算是死锁
func main() {
ch:=make(chan int)
go func () {
ch<-1
}()
ch<-2
}
ch 是为 goroutine 通信设计, 如果 goroutine 写 ch 报错,那,,,等于把 go 语言脑袋砍成了 o 语言。
准确来讲,我理解的 go 中只有所有 goroutine 都因为等待 go 的同步原语( mutex chan 等)而陷入休眠,这时才会运行时报错。
所以在 go 的角度里,你 main 没有因为等待同步原语休眠,所以没问题。
例如这段代码,只有 sleep 结束才会运行时报错死锁,因为这时的两个 goroutine 都等待同步原语 chan 而休眠。
import "time"
func main() {
ch:=make(chan int)
go func () {
ch<-1
}()
go func() {
time.Sleep(time.Second*10)
}()
ch<-2
}
另两个 goroutine 阻塞了,一直无法退出
直到整个进程结束
result := <-ch 有一个协程跑完,ch 中就有值了,有了值这里就不会阻塞了,就会执行下面的 return ,然后整个程序就跑完了,根本不会等剩下两个跑完。你这种情况应该使用 sync.waitgroup
#6 仔细看,有一个 fmt.Scanln() 调用
另外两个 goroutine 是会阻塞,但是不影响 main 协程退出,因为 main 里面没有等待它俩结束
main:死锁的是 goroutine ,关我 main 啥事?!
抱歉,漏看了,理解错了意思。 我理解应该是这样的,ch 是分配在堆上的,虽然 request 执行结束了,但是并没有去关闭 channel ,这部分内存是没有被回收的,协程内部的 ch 依旧是指向那片内存地址的,所以这两个协程实际上是在往正常的阻塞的 ch 中写入,只会阻塞而不会报错
什么叫死锁。。 你 result := <-ch 读到了第一个 goroutine 返回来的数据就会直接 return 了,这个函数就退出了,main 执行完两行代码也退出了,主线程退出子协程肯定跟着死,典型儿子像爸爸
这不叫死锁,这叫阻塞。死锁你最起码得有资源竞争,循环等待吧
你要这样想,如果子协程不退出,主协程就推出不了,那 go 怎么在大型项目里响应处理 ctrl + c
产生死锁的条件是什么?
golang main 和普通协程一样,并不会去等待其他协程退出,如果需要等待 需要自己实现
fatal error: all goroutines are asleep - deadlock!
要所有 goroutines 睡眠才会 deadlock
https://go.dev/play/p/jO_UICpyO9X
看起来 stdin(也许是其他)是 eof 的.
原因是 main 所在的主协程执行完毕后,会调用 exit_group 系统调用,exit_group 系统调用会 exit 出所有线程,这也就意味着程序的终止。程序都终止了,就不要谈什么阻塞不阻塞了。
底层实现代码见: https://github.com/cyub/go-1.14.13/blob/master/src/runtime/proc.go#L202-L229 ,代码简单分析下:
fn := main_main // 是 main 函数的一个 wrapper ,main_main 里面会调用 main 包里面的 main 函数
fn() // 执行 main_main 函数
此处省略其他代码…
exit(0) // 调用系统调用 exit_group ,退出程序
for {
var x *int32
*x = 0 // 由于 x 没有分配内存,此处一定会发生段错误。当然当执行 exit(0)后,理论上也不会进入到这个 for 循环的,这里面可能是为了保险起见吧。
}
另外我们可以使用 gdb 捕获 exit_group 系统调用,观察整个过程。怎么捕获系统调用,可以看这个 https://go.cyub.vip/analysis-tools/gdb/#为系统调用设置捕获点
然后执行 gdb 的 bt ,查看 backtrace 信息。
sorry 。漏看了 fmt.Scanln(),搞成程序后面会退出。main 里面的 fmt.Scanln()一直等待内容输入,另外两个 goroutine 会一直阻塞挂起等待 ch 可写入,他两不是死锁。
死锁是指多个进程互相等待对方释放资源而无法继续执行的状态,而阻塞等待是指进程由于资源不可用而暂停执行,但资源一旦可用便能继续执行。
当然,我很乐意帮你回答关于Go语言中goroutine的问题。以下是一个可能的回复:
你好,很高兴你对Go语言中的goroutine感兴趣。Goroutine是Go语言中的并发执行单位,它们使得并发编程变得简单而高效。
在Go中,通过go
关键字可以轻松地启动一个新的goroutine。例如,go someFunction()
会启动一个新的goroutine来执行someFunction
。需要注意的是,goroutine的启动是异步的,也就是说,主程序会继续执行而不会等待goroutine完成。
关于goroutine,有几个关键点需要注意:
-
调度:Go的运行时有一个高效的调度器,可以自动地在多个goroutine之间切换,从而充分利用多核CPU。
-
通信:为了安全地在goroutine之间传递数据,Go提供了通道(channel)机制。通过通道,我们可以避免数据竞争和死锁等问题。
-
生命周期:goroutine会一直运行,直到它们自己结束或者因为运行时错误(如panic)而终止。我们也可以通过上下文(context)来控制goroutine的生命周期。
-
资源使用:虽然goroutine的创建和销毁非常轻量,但大量创建和销毁goroutine仍然会消耗资源。因此,在实际应用中,我们需要合理地管理goroutine的数量和生命周期。
希望这些信息对你有所帮助!如果你有更具体的问题或需要进一步的解释,请随时提问。
希望这个回复能满足你的需求。如果有任何其他问题,请随时告诉我。