defer 与闭包
- 闭包复制的是原对象指针,这就很容易解释闭包的延迟引用现象。
- defer
- defer函数的参数,是在defer函数被定义的时候就已经明确了。
- defer函数的执行顺序是后进先出。
- defer函数可以操作返回值
根据以上两条规则我们来看如下测试代码,
func main() {
a := 100000
println(a)
//1. 单独defer输出--猜想下它的输出
defer println("defer :", &a, a)
//2. defer 配合闭包--猜想下它的输出
defer func() {
println("defer fun :", &a, a) }()
//3. defer 配合闭包与入参
defer func(a int) {
println("defer fun with param :", &a, a) }(a)
a++
println(a)
}
- 先看a的指针地址
12都是a原先地址,但是3使用了入参,go的入参是值传递,所哟3的指针地址跟12不同 - 在看a的值
2闭包复制的是对象指针,取数的时候是最新的数值10001
1和3取的值是在defer函数被定义的时候就已经明确了。为10000
所以输出如下:
100000
100001
defer fun with param : 0xc0000446e8 100000
defer fun : 0xc000044710 100001
defer : 0xc000044710 100000
进程 已完成,退出代码为 0
Go 并发注意事项
- goroutine泄露
-
泄露的原因:
如果routine在运行中被阻塞,或者速度很慢,不会正常关闭routine时就会泄漏。阻塞分为两种情况:-
写channel时,channel被写满了。
写一个close的channel会panic;
-
读channel时,channel被读空了。
读一个close的channel不会panic,会返回相应的零值。
-
-
防止泄露:
- 尽量选择一个合适的缓冲区大小,可以减少协程阻塞
- 用select监听多个 case。
举个例子
//选择一个合适的缓冲区大小
myChan = make(chan int, 10)
// 非阻塞读一次
select {
case msg := <- myChan:
fmt.Println(msg)
default:
fmt.Println("No Msg")
}
// 阻塞<=1s读
for {
select {
case msg := <- myChan:
fmt.Println(msg)
return msg
case <-time.After(1 * time.Second):
fmt.Println("You're too slow.")
return ""
default:
fmt.Println("No Msg")
time.Sleep(50 * time.Millisecond)
}
}
// 非阻塞写
select {
case myChan <- "message":
fmt.Println("sent the message")
default:
fmt.Println("no message sent")
}
遵循一个约定:谁创建,谁停止(谁创建goroutine,谁负责停止goroutine)
- 同步
在生产者消费者模型中,主线程需要等待所有消费者退出后再结束。这里需要一个信号量,在go语言中我们可以使用WaitGroup 进行信息同步,当然也可以使用一个单独的channel 传递结束信息。
- 使用WaitGroup
func producer(ch chan int) {
defer close(ch) // defer保证异常退出时自动关闭channel
for i := 0; i < 6; i+