群友提出的问题 ,很感兴趣,这里来记录一下,等有时间的时候仔细研究一下
背景:有结构体structure,它有一个方法method,在method中有一个for{}无限循环,循环体内使用channel监听输入并做相应处理。
提问:s是structure的实例化对象,当执行s.method()之后,如果将s置空或者删除,for{}无限循环或者channel监听会退出/停止吗?
method如果使用到了struct里面的变量:使用到了:
type structure struct {
ch chan string
}
func (s *structure) method {
for {
input := <- s.ch
fmt.Println(input)
}
}
疑问点在于:当goroutine阻塞在`input := <- s.ch`的时候,如果实例被置空或者删除了,接下来会发生什么?
1. s.ch跟着一起灰飞烟灭了?
2. channel阻塞突然消失了?
3. for循环退出了还是无阻塞的继续跑下去?
群友的测试结果:
1. s.method()本身不会panic。input := <- s.ch正常。
2. for{}循环不会被阻塞,也不会退出。直到main结束for{}循环才会结束。
3. 会发生panic,在s被置空后往s.ch流入数据时
测试代码
package main
import (
"fmt"
"time"
"runtime"
"runtime/debug"
)
type T struct {
ch chan int
}
func (t *T) f() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 1<<16)
stackSize := runtime.Stack(buf, true)
fmt.Printf("Error in f(): %v.\nCall Stack: %+v.", r.(error), string(buf[0:stackSize]))
}
}()
// timer := time.NewTicker(time.Millisecond * 10)
counter := 0
for {
select{
case input := <- t.ch:
fmt.Printf("receive %d\n", input)
counter++
// case <- timer.C:
// fmt.Printf("idle, count = %d\n", counter)
}
}
fmt.Printf("exit f()")
}
func main() {
// test0() // panic (t.ch is invalid after t = nil)
// test1() // panic (t.ch is invalid after t = nil)
// test2() // blocked (chans["test"] is not exist after delete(chans, "test")) till timeout then normal exit
test3() // sleep till timeout then normal exit
}
func test0() {
ch := make(chan int)
chans := map[string] chan int{"test": ch}
t := &T{ch: chans["test"]}
go func() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 1<<16)
stackSize := runtime.Stack(buf, true)
fmt.Printf("Error: %v.\nCall Stack: %+v.", r.(error), string(buf[0:stackSize]))
}
}()
for i := 0; i < 1000; i++ {
fmt.Printf("send %d\n", i)
t.ch <- i
time.Sleep(time.Millisecond * 10)
}
}()
go t.f()
time.Sleep(time.Second * 2)
fmt.Printf("---------------------> delete it\n")
t = nil
delete(chans, "test")
fmt.Printf("---------------------> delete done\n")
time.Sleep(time.Second * 20)
}
func test1() {
debug.SetGCPercent(-1)
ch := make(chan int)
chans := map[string] chan int{"test": ch}
t := &T{ch: chans["test"]}
go func() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 1<<16)
stackSize := runtime.Stack(buf, true)
fmt.Printf("Error: %v.\nCall Stack: %+v.", r.(error), string(buf[0:stackSize]))
}
}()
for i := 0; i < 1000; i++ {
fmt.Printf("send %d\n", i)
t.ch <- i
time.Sleep(time.Millisecond * 10)
}
}()
go t.f()
time.Sleep(time.Second * 2)
fmt.Printf("---------------------> delete it\n")
t = nil
delete(chans, "test")
runtime.GC()
fmt.Printf("---------------------> delete done\n")
time.Sleep(time.Second * 20)
}
func test2() {
debug.SetGCPercent(-1)
ch := make(chan int)
chans := map[string] chan int{"test": ch}
t := &T{ch: chans["test"]}
go func() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 1<<16)
stackSize := runtime.Stack(buf, true)
fmt.Printf("Error: %v.\nCall Stack: %+v.", r.(error), string(buf[0:stackSize]))
}
}()
for i := 0; i < 1000; i++ {
fmt.Printf("send %d\n", i)
chans["test"] <- i
time.Sleep(time.Millisecond * 10)
}
}()
go t.f()
time.Sleep(time.Second * 2)
fmt.Printf("---------------------> delete it\n")
t = nil
delete(chans, "test")
runtime.GC()
fmt.Printf("---------------------> delete done\n")
time.Sleep(time.Second * 20)
}
func test3() {
debug.SetGCPercent(-1)
ch := make(chan int)
chans := map[string] chan int{"test": ch}
t := &T{ch: chans["test"]}
go func() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 1<<16)
stackSize := runtime.Stack(buf, true)
fmt.Printf("Error: %v.\nCall Stack: %+v.", r.(error), string(buf[0:stackSize]))
}
}()
for i := 0; i < 1000; i++ {
fmt.Printf("send %d\n", i)
ch <- i
time.Sleep(time.Millisecond * 10)
}
}()
go t.f()
time.Sleep(time.Second * 2)
fmt.Printf("---------------------> delete it\n")
t = nil
delete(chans, "test")
runtime.GC()
fmt.Printf("---------------------> delete done\n")
time.Sleep(time.Second * 20)
}