编程思想架构如果是内功的话,那对一门语言的掌握与精通就是外功招式。光有内功,外功不扎实,那就是有劲使不出;同样的光有外功,内功不深厚那就是外强中干。所以在此记录下遇到的关于golang中使用的一些问题,属于外功招式了。希望大家都能从中受益,一直会持续更新下去。
Interface篇
接口判空nil
接口是由类型和值组成,只要其中任意一个部分不为nil
,则接口判定则不会nil
下述代码执行结果是not nil
,因为返回的是 RoundFace 具体的类型,所以 Face 就不为nil,但是如果15行改成var rf Face
那么判断就会是nil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
type Face interface {
FaceShape() string
}
type RoundFace struct {
}
func (rf RoundFace) FaceShape() string {
return "round"
}
func GetFace() Face {
var rf RoundFace
return rf
}
func main() {
if GetFace() == nil {
println("nil")
} else {
println("not nil")
}
}
Chan篇
chan关闭陷阱
chan只可以被关闭一次
这个估计是被说的做多的,chan多次关闭的话就会直接panic 这个倒是没啥说的,一般都会配合once保证channel只会被close一次。
正确示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
"sync"
)
func main() {
var once sync.Once
ch := make(chan struct{}, 1)
ch <- struct{}{}
once.Do(func() {
close(ch)
})
}
chan关闭后,receive 接收零值,导致for - select泄露
for-select是一段很常用的代码,可以发现如果使用 select 进行接收,即使关闭了channel之后,receive还是会一直收到零值,从而导致不会退出
正确示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan struct{}, 1)
ch <- struct{}{}
close(ch)
for {
select {
case zero, open := <-ch:
fmt.Println(open)
// 当channel被关闭后,open 会变成false
if !closed {
return
}
fmt.Println(zero)
time.Sleep(time.Second)
}
}
}
错误示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan struct{}, 1)
ch <- struct{}{}
close(ch)
for {
select {
case zero, open := <-ch:
fmt.Println(zero)
fmt.Println(open)
time.Sleep(time.Second)
}
}
}
输出如下
1
2
3
4
5
6
7
8
9
10
11
12
{} #塞入的数值
true #当前通道未关闭
{} #关闭后返回的零值
false #当前通道已关闭
{}
false
{}
false
{}
false
{}
false
http 篇
http 同域名TCP连接复用
响应一定需要 读取处理,及时不需要,也需要读取下io.Copy(io.Discard, resp.Body)
,不然及时服务端支持keep_alive
,go http client 也不会复用TCP连接
错误代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
import (
"net/http"
"time"
)
func main() {
client := http.DefaultClient
// 创建两个tcp连接
go request(client)
go request(client)
// 间隔5s让前面两个http请求处理完毕
time.Sleep(5 * time.Second)
// 正常来说下面的两个http请求会复用上面的tcp链接
go request(client)
go request(client)
time.Sleep(6 * time.Second)
}
func request(client *http.Client) {
resp, _ := client.Get("https://www.baidu.com")
defer resp.Body.Close()
// 未读取response.Body
//io.Copy(io.Discard, resp.Body)
}
错误现象
可以看到在首次tcp连接创建完后,间隔5s还是会重新创建新的tcp连接
正确代码
可以发现只有在32行有不一样的地方读取处理了响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main
import (
"io"
"net/http"
"time"
)
func main() {
client := http.DefaultClient
// 创建两个tcp连接
go request(client)
go request(client)
// 间隔5s让前面两个http请求处理完毕
time.Sleep(5 * time.Second)
// 正常来说下面的两个http请求会复用上面的tcp链接
go request(client)
go request(client)
time.Sleep(6 * time.Second)
}
func request(client *http.Client) {
resp, _ := client.Get("https://www.baidu.com")
defer resp.Body.Close()
// 读取response.Body
io.Copy(io.Discard, resp.Body)
}
正确现象
map篇
在使用未知map之前,需要先判断下是否为nil指针,预防空指针
1
2
3
4
5
if cr.Annotations != nil {
if v, ok := cr.Annotations["key"]; ok && v == "false" {
return false
}
}
slice篇
reflect.DeepEqual
slice判断是否相同,注意顺序问题
对于slice
元素内容一样,但是顺序不同,reflect.DeepEqual
返回的是false
,可以使用 slices.Sort
先整体排序完在进行比较
1
2
3
4
5
6
7
8
9
10
11
12
13
var s1 = []int{1, 2, 3}
var s2 = []int{2, 1, 3}
func TestDeepEqual() {
log.Println(reflect.DeepEqual(s1, s2)) // false
}
func TestDeepEqualWithSort() {
slices.Sort(s1)
slices.Sort(s2)
log.Println(reflect.DeepEqual(s1, s2)) // true
}
如果你看不到评论,那么就真的看不到评论w(゜Д゜)w