这是我参与「第五届青训营」笔记创作活动的第1天
defer
- defer 的执行顺序
func fun() {
//defer在函数return后从下到上依次运行,通常可做资源释放等
defer last()
defer middle()
defer first()
//最先运行
fmt.Println("hello world")
}
// go fun() // 开启一个 coroutine
- defer 的具体执行时间
package main
import "fmt"
func main() {
var f = func() (i int) {
defer func() { i++ } ()
defer func() { fmt.Println(i) } () // 1
return 1
}
fmt.Println(f()) // 2
}
输出为
1
2
defer 在所在函数块返回后,caller逻辑执行前执行
sync.WaitGroup
协调多个执行流同步
协程,用户级子线程。和普通线程有很多相似之处。
开启子线程(协程)后,主协程可能需要等待子协程处理完毕才能继续运行,或者要接受子协程的输出结果,主协程才能结束执行。
朴素的方法:使用time.Sleep(ns)阻塞主协程一段合理的时间,给子协程足够的时间完成执行。
正确的方法:
类似C中pthread库的pthread_join(),Java中的CyclicBarrier,CountDownLatch方法
Go提供了sync.WaitGroup来(等待)同步多个协程的运行完成。本质是基于计数。
var wg sync.WaitGroup
wg.Add(等待任务个数)
for i = 1; i <= 任务个数; i++
其中WaitGroup的Add方法表示增加需要等待的任务的个数。Down方法使任务计数减1,表示完成一个任务。Wait方法阻塞当前协程,等待WaitGroup中设定的任务数减到0(都执行完毕)。
WaitGroup的实现原理[1]
loop variable captured by func literal
使用循环变量开启多个协程的常见问题
func main() {
arr := []int{1, 2, 3, 4}
for _, i := range arr {
go func() { fmt.Println(i) }()
}
time.Sleep(time.Second)
}
可能会输出4\n3\n4\n4,4\n4\n4\n4等,因为匿名函数里使用的i绑定的是外层循环变量i本身,并没有对i进行拷贝,协程运行时访问i的值时,i已经随循环改变。这就是loop variable captured by func literal
简单的解决办法,对i进行拷贝,为func添加参数i,并在调用时传参
或手动拷贝
for _, i := range arr {
i := i // 循环体新变量i,循环变量i的范围大于循环体,起始于for
go func() { fmt.Println(i) }()
}
Notice
go的语法设计
channel <- var
将数据 var 写入通道 channel,但代码写起来是反着的,通道 被写入 数据,有种强行被动语态的赶脚
make
make明明可以很好地伪装成一个函数,提供一个统一的视觉/使用体验
s := make([]string, 3)
// 但在 Channel 这里
out = make(chan int)
buf = make(chan int, 3)
// 这样是不是更统一呢
out = make(chan, int, 3)
难道上面突出了make不同之处的语法更好解析吗
两个工具
网络请求代码生成 Convert curl to Go (curlconverter.com)
JSON转struct类型 JSON转Golang Struct - 在线工具 - OKTools