这几年主要从事golang的后台开发,这里总结一下golang的一些特性,这篇文章不会面面俱到,只是把我认为重要的点记录下来。
TODO
TODO
通道本身是并发安全的,在声明并初始化通道的时候,需要用make。通道包含容量,容量为0的称作非缓冲通道,容量大于0的,称作缓冲通道。
非缓冲通道需要发送和接收成对的时候才正式执行
ch := make(chan int)
go func() {
ch <- 1
}()
go func() {
x :<- ch
}
也就是,对于发送chan协程来说,接收协程没有ready, 它是不执行的,对于接收chan协程来说,发送协程没有ready, 它也是不执行的。
带缓冲的通道发送端只要缓冲区没满,就可以发送数据而不阻塞,接收方只要缓冲区有数据,就能接收数据不阻塞。
ch := make(chan int,3)
go func() {
ch <- 1
ch <- 2
ch <- 3
}()
go func() {
x :<- ch
y :<- ch
z :<- ch
}
通道的一些异常操作情况总结:
nil | empty | full | not full | closed | |
---|---|---|---|---|---|
接收 | 阻塞 | 阻塞 | 不阻塞 | 不阻塞 | 返回未读的元素,读完后返回零值 |
发送 | 阻塞 | 成功 | 阻塞 | 成功 | panic |
关闭 | panic | 关闭成功,无未读元素 | 关闭成功,有未读元素 | 关闭成功,有未读元素 | panic |
select 和channel的结合使用:
select中如果只有case 语句,如果所有case对应的channel均无值返回则select阻塞。如果有default语句,则没有事件的时候,会执行default语句。
如果select中包含多条case语句有值返回,select为了公平性,会随机选择一个case语句执行,而不是按case语句的顺序执行。
golang的程序如果出现一些异常,比如数组越界,nil pointer访问等,就会发生panic, panic会导致程序崩溃,即使panic发生在子协程中,也会导致整个进程崩溃。
因此,我们在实际项目开发的时候,都会用recover的方式截获panic, 避免程序崩溃,这就有点类似java中的try...catch了。
func (s Service) GetCommentList(ctx context.Context, req *proto.GetCommentListReq) (resp *proto.GetCommentListResp,
err error) {
defer func() {
if e := recover(); e != nil {
log.Errorf(ctx, "got_a_panic|err=%v", common.GetStackInfo())
err = access.NewError(ctx, common.ErrorInternalError, "server recover panic")
}
}()
resp, err = comment.GetCommentList(ctx, req)
if err != nil {
err = access.NewError(ctx, common.ErrorGetCommentList, err.Error())
}
return resp, err
}
// 其中common.GetStackInfo()的实现
func GetStackInfo() string {
var buf [4096]byte
n := runtime.Stack(buf[:], false)
return string(buf[:n])
}
我们看到,这里用到了defer, defer的执行顺序是按照下面的规则的:
1. 多个defer的执行顺序为“后进先出”;
2. 所有函数在执行RET返回指令之前,都会先检查是否存在defer语句,若存在则先逆序调用defer语句进行收尾工作再退出返回;
3. 匿名返回值是在return执行时被声明,有名返回值则是在函数声明的同时被声明,因此在defer语句中只能访问有名返回值,而不能直接访问匿名返回值;
4. return其实应该包含前后两个步骤:第一步是给返回值赋值(若为有名返回值则直接赋值,若为匿名返回值则先声明再赋值);第二步是调用RET返回指令并传入返回值,而RET则会检查defer是否存在,若存在就先逆序插播defer语句,最后RET携带返回值退出函数;
因此,defer、return、返回值三者的执行顺序应该是:return最先给返回值赋值;接着defer开始执行一些收尾工作;最后RET指令携带返回值退出函数。
匿名返回值的情况:
package main
import (
"fmt"
)
func main() {
fmt.Println("a return:", a()) // 打印结果为 a return: 0
}
func a() int {
var i int
defer func() {
i++
fmt.Println("a defer2:", i) // 打印结果为 a defer2: 2
}()
defer func() {
i++
fmt.Println("a defer1:", i) // 打印结果为 a defer1: 1
}()
return i
}
有名返回值的情况:
package main
import (
"fmt"
)
func main() {
fmt.Println("b return:", b()) // 打印结果为 b return: 2
}
func b() (i int) {
defer func() {
i++
fmt.Println("b defer2:", i) // 打印结果为 b defer2: 2
}()
defer func() {
i++
fmt.Println("b defer1:", i) // 打印结果为 b defer1: 1
}()
return i // 或者直接 return 效果相同
}
TODO
TODO
TODO
TODO