slice

1
2
3
4
// a[low : high : max]
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path,'/')
dir1 := path[:sepIndex:sepIndex] //Full slice expression, last index is capacity

append

golang的append不是值拷贝,特别是在append函数入参的时候,尽量使用copy函数先处理一下再append,否则会踩很多坑。

slice的核心内容有两点:

  • 函数为值复制传递
  • slice的属性包含指向底层数组的指针、cap和len

结构体

结构体比较大小

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import (
    "fmt"
    "reflect"
)
func main() {
    v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2))
    //prints: v1 == v2: true
    m1 := map[string]string{"one": "a","two": "b"}
    m2 := map[string]string{"two": "b", "one": "a"}
    fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2))
    //prints: m1 == m2: true
    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2))
    //prints: s1 == s2: true
}

mutex

最近在工作中发现有部分goroutine没有执行到的情况,而且非必现,在测试过程中没有出现该问题,上线后就出现了:dog: ,就是这么神奇。经过一番排查发现可能是mutex使用有问题,出现了死锁,因为该问题及其偶发,所以先增加了一个监控死锁的脚本定时判断是否出现死锁,同时针对可能的死锁进行了修改,查询了网上关于锁的注意事项在此整理。

golang的死锁监控

这部分参考了这篇文章,是通过pprof定时获取程序中的goroutine情况,检查其中是否存在sync.runtime_SemacquireMutex,它表示信号量阻塞在当前的gouroutine。出现这个信号不一定表示出现死锁,也可能是刚好处在等待锁的状态,只有长时间出现才会证明死锁了。

锁的使用注意

  • 尽量减少锁的持有时间,不在持有锁期间进行IO操作

  • 使用defer来进行锁的释放

  • 适当时使用读写锁

  • copy结构体可能导致非预期的死锁!!

     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
    
    //copy结构体的时候,如果结构体中有锁,需要谨慎,多测试
    package main
      
    import (
        "fmt"
        "sync"
    )
      
    type User struct {
        sync.Mutex
        name string
    }
      
    var UserArr = make(map[string]*User)
      
    func main() {
        UserArr["a"] = &User{
            Mutex: sync.Mutex{},
            name:  "A",
        }
        test1 := UserArr["a"]
        test1.Lock()
        fmt.Println(test1.name)
        defer test1.Unlock()
      
        test2 := UserArr["a"]
        test2.Lock()            //死锁,同一个锁对象,多次lock
        fmt.Println(test2.name)
      
        defer test2.Unlock()
    }
    
  • 使用go vet检查锁的使用问题

  • 使用 -race检查数据竞争

  • 改为使用channel,减少锁的使用

  • 针对读多写少的场景,使用sync.Map代替map加锁

Reference

https://mozillazg.com/2019/04/notes-about-go-lock-mutex.html

https://blog.csdn.net/u013536232/article/details/107868474

https://mp.weixin.qq.com/s/TzHvDdtfp0FZ9y1ndqeCRw

channel

无限缓存的channel

这是个学习channel,select用法的很好案例!!大力推荐

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package test_channel

// https://github.com/smallnest/chanx/blob/main/unbounded_chan.go
// used for generic type
type T interface{}

type UnboundedChan struct {
	In     chan<- T // channel for write
	Out    <-chan T // channel for read
	buffer []T
}

func (c UnboundedChan) Len() int {
	return len(c.buffer) + len(c.Out)
}

func (c UnboundedChan) BufLen() int {
	return len(c.buffer)
}

func NewBoundedChan(initCapacity int) UnboundedChan {
	in := make(chan T, initCapacity)
	out := make(chan T, initCapacity)
	ch := UnboundedChan{
		In:     in,
		Out:    out,
		buffer: make([]T, 0, initCapacity),
	}

	go func() {
		defer close(out)

		for {
			val, ok := <-in
			if !ok {
				// in channel closed
				break
			}

			select {
			case out <- val: //out没有满,还可以写入,buffer中也没数据
				continue
			default:
			}

			//以下为out满的时候
			ch.buffer = append(ch.buffer, val)

			for len(ch.buffer) > 0 {
				select {
				case val, ok := <-in:
					if !ok {
						break // in closed
					}
					ch.buffer = append(ch.buffer, val)
				case out <- ch.buffer[0]:
					ch.buffer = ch.buffer[1:]
					if len(ch.buffer) == 0 {
						ch.buffer = make([]T, 0, initCapacity)
					}
				}
			}

		}

		for len(ch.buffer) > 0 {
			out <- ch.buffer[0]
			ch.buffer = ch.buffer[1:]
		}
	}()

	return ch
}

Reference

https://colobu.com/2021/05/11/unbounded-channel-in-go/

http

服务上线测试时主要观察的几点:

  1. 功能实现
  2. 关键日志和监控点
  3. cpu和内存使用情况
  4. 连接数和goroutine数量

今天测试上线了一个小服务,发现goroutine数量缓慢增长,tcp的CLOSE_WAIT数量也增加

pprof监控

1
2
3
4
5
goroutine 80318 [select, 5 minutes]:
net/http.(*persistConn).writeLoop(0xc008d46240)
	/usr/local/go/src/net/http/transport.go:2340 +0x11c
created by net/http.(*Transport).dialConn
	/usr/local/go/src/net/http/transport.go:1709 +0xcdc

http response.Body一定要close,只要client.Do不返回error,后面的response.Body都要close

当没有去read response Body的时候,可以通过一个CancelFunc去主动cancel掉

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
request, err := http.NewRequest("GET", url, bytes.NewReader([]byte("")))
	if err != nil {
		return ""
	}
	ctx, cancelFunc := context.WithCancel(context.Background())
	request = request.WithContext(ctx)
	client := http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return ""
	}
	defer cancelFunc()

	if resp.StatusCode != http.StatusOK {
		return ""
	}