查看goroutine数量

runtime.NumGoroutine()

设置执行Goroutine的最多cpu数目

runtime.GOMAXPROCS(2)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func GOMAXPROCS(n int) int {
	if GOARCH == "wasm" && n > 1 {
		n = 1 // WebAssembly has no threads yet, so only one CPU is possible.
	}

	lock(&sched.lock)
	ret := int(gomaxprocs)
	unlock(&sched.lock)
	if n <= 0 || n == ret { //设为0或负数时默认为机器最大cpu数目
		return ret 
	}

	stopTheWorld("GOMAXPROCS")

	// newprocs will be processed by startTheWorld
	newprocs = int32(n)

	startTheWorld()
	return ret
}

包管理工具godep常用命令

godep save 初始化或,生成Godeps和vendor目录

godep restore 按照Godeps.json文件go get

Go Cross Compile

1
2
3
4
 GOOS=linux GOARCH=amd64 go build hello.go
 
 # beego交叉编译打包
 bee pack -be GOOS=linux -be GOARCH=amd64

Go数据类型

  • bool
  • uint/int
  • byte
  • Intx : x代表8,16,32,64其中一个
  • floatx: x为32和64其中一个
  • complex64/complex128: 复数类型
  • uintptr
  • arrary/struct/string
  • 引用类型slice, map, channel
  • 接口: interface
  • 函数:func

浮点数计算精度有点坑,

1
2
3
4
5
6
a := 8.89 //默认计算机位数
fmt.Println(a + 0.3)
fmt.Println(8.899 + 0.3)

9.190000000000001
9.19

位运算

  • 位移:要位移的值是有符号值时,Go自动应用算术位移
  • 取反:^a, 在 Go 中 x = 1 ^ x 可以翻转该位
  • 异或:a^b.
1
32 << (^uint(0) >> 63)  // 可能位32或64,取决于平台位数

生成随机数

在package rand里,如rand.Int(),rand.Intn(n int)

终端输入

 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
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
  fmt.Println(foo(foo(input.Text())))
}


nums := [9][9]int{}
for i := 0; i < 9; i++ {
  for j := 0; j < 9; j++ {
    fmt.Scanf("%d", &nums[i][j])
  }
}

//输入整数数字,回车一次处理一次
input := bufio.NewScanner(os.Stdin)
	nums := []int{}
	for input.Scan() {
		numStrs := strings.Split(input.Text(), ",")
		for _, str := range numStrs {
			num, _ := strconv.Atoi(str)
			nums = append(nums, num)
		}

		fmt.Println(nums)
		nums = []int{}
	}

Go mod

实现可以在任意目录下建立go项。当然GOPATH变量还是要存在的。$GOPATH/pkg目录下会有一个mod目录

1
2
3
4
5
6
7
8
go mod download    下载依赖的module到本地cache默认为$GOPATH/pkg/mod目录
go mod edit        编辑go.mod文件
go mod graph       打印模块依赖图
go mod init        初始化当前文件夹, 创建go.mod文件
go mod tidy        增加缺少的module删除无用的module
go mod vendor      将依赖复制到vendor下
go mod verify      校验依赖
go mod why         解释为什么需要依赖

go mod经常会因download失败,最好加少代理,比较优秀的代理网站如 https://goproxy.io/zh/、https://goproxy.cn/ 等,若失败多试试几个代理网站,因为有的代理网站有可能没有你要的模版也会download失败。

字符串拼接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
join1 := s1 + s2

join2 := fmt.Sprintf("%s%s", s1, s2)

ss := []string{s1, s2}
join3 := strings.Join(ss, "")

var builder strings.Builder
builder.WriteString(s1)
builder.WriteString(s2)
fmt.Println(builder.String())

var buf bytes.Buffer
buf.WriteString(s1)
buf.WriteString(s2)
fmt.Println(buf.String())

效率方面这里有介绍»

但我用我下面方法计算耗时,好像跟资料不符,不知道是哪出了问题

1
2
3
4
5
6
7
8
9
func computeTime(desc string, function func()) {
	start := time.Now()

	function()

	duration := time.Since(start)
	fmt.Printf("%s %s\n\n", desc, duration.String())
}

设计模式

https://legacy.gitbook.com/book/hxangel/go-patterns/details

最值

1
2
3
int(^uint(0)>>1) 最大值

^(int(^uint(0)>>1))最小值

初始化二维数组

没有简易的方法给数组初始化特定值

1
2
3
4
5
6
f := make([][]int, n)
for i := 0; i < len(matrix); i++ {
  f[i] = make([]int, n)
}
f := [n][n]int //n必须为常量

位运算

1
2
3
res >>= 2 //右移

res <<= (32 - bitN) //左移

interface排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package sort

// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less reports whether the element with
	// index i should sort before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}

比如对二维数组排序,以第一个元素排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type Nums2VList [][]int

func (s Nums2VList) Len() int {
	return len(s)
}

func (s Nums2VList) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s Nums2VList) Less(i, j int) bool {
	return s[i][0] < s[j][0]
}

list := Nums2VList([][]int{a1, a2, a3,...})
sort.Sort(list)

程序在零点触发执行

1
2
3
4
5
6
7
8
9
for {
 		doSomething()
  
		now := time.Now()
		next := now.Add(time.Hour*24)
		next = time.Date(next.Year(),next.Month(),next.Day(),0,0,0,0,next.Location())
		t := time.NewTicker(next.Sub(now))
		<- t.C
}

初始化

1
2
var arr []int //arr长度为0,但为nil
arr := make([]int, 0) //长度为0,但不为nil map亦如此

不过奇怪的是arr可以使用append 操作,而map不能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
	var data []int
	fmt.Println(data == nil) //true
	data = append(data, 1)
	fmt.Print(data)

	var p map[int]bool //panic: assignment to entry in nil map
	fmt.Println(p == nil) //true
	p[1] = true
}

对于slice,当json解析时 a nil slice encodes to null, while []string{} encodes to the JSON array []

强制转换

1
2
3
4
5
6
	a := -1
	_ = uint(a) // why no panic?
	var b uint
	b = uint(a)
	fmt.Println(b)
	_ = uint(-1) // panics: main.go:7: constant -1 overflows uint

the reason»

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
type Foo string

func f(a Foo) {}

func main() {
	f("sarkozy")

	const t = "julie gayet"
	f(t)

	s := "hollande"

	//compile error
	f(s)

	f(Foo(s)) // ok
}

the reason»

map无法取址

map无法取得对象地址,故无法对其进行修改

1
2
3
4
5
6
7
8
9
var m = map[string]struct{x, y int} {
    "foo": {2, 3},
}

func main() {
 		fmt.Println(m["foo"].x)
    m["foo"].x = 4 // cannot assign to m["foo"].x
    fmt.Println(m["foo"].x)
}

需要改为

1
2
3
var m = map[string]*struct{x, y int} {
    "foo": {2, 3},
}

»

atomic原子操作

原子操作由底层硬件支持,而锁则由操作系统提供的API实现。显然原子操作效率高,但作用域小,且功能少。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// PutMessage writes a Message to the queue
func (t *Topic) PutMessage(m *Message) error {
	t.RLock()
	defer t.RUnlock()
	if atomic.LoadInt32(&t.exitFlag) == 1 {
		return errors.New("exiting")
	}
	err := t.put(m)
	if err != nil {
		return err
	}
	atomic.AddUint64(&t.messageCount, 1) 
	atomic.AddUint64(&t.messageBytes, uint64(len(m.Body)))
	return nil
}

golamg atomic uint64原子操作没有减法,减法操作如下

1
2
3
4
var x uint64 = 90
var c uint64 = 2
atomic.AddUint64(&x, ^uint64(c-1)) //90-2
fmt.Println(x)

»

var不需要指定类型

1
var dp = [1001][1001]int{} //自动判别类型

select chan

select加break和不加效果一样,若加了break,break只跳出select,并不能跳出select的外循环;若没有default,程序会阻塞到chan非空为止

若想要跳出for循环,可以用goto+point或break+指定循环

 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
// break+指定循环
EXIT_FOR:
for {
  fmt.Println("select")
  select {
    case <-ch2:
  case <-ch1:
    break EXIT_FOR
    // default:
  }
}
w.Done()
	

// goto
for {
  fmt.Println("select")
  select {
    case <-ch2:
  case <-ch1:
    goto EXIT
    // default:
  }
}
EXIT:
w.Done()

defer

defer在return后执行,在函数结束前执行。执行return res时,将res的赋给函数返回变量。

 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
package main

import "fmt"

func main() {
    fmt.Println(DeferFunc1(1))
    fmt.Println(DeferFunc2(1))
    fmt.Println(DeferFunc3(1))
    DeferFunc4()
}

func DeferFunc1(i int) (t int) { //返回变量位t
    t = i
    defer func() {
        t += 3
    }()
    return t //这一步t是多余的,相当于t=t
}

func DeferFunc2(i int) int { 
    t := i
    defer func() {
        t += 3
    }()
    return t //将t的值给函数返回变量,故defer中再对t修改时,是没有
}

func DeferFunc3(i int) (t int) { 
    defer func() {
        t += i
    }()
    return 2
}

func DeferFunc4() (t int) {
    defer func(i int) {
        fmt.Println(i)
        fmt.Println(t)
    }(t)
    t = 1
    return 2
}

详解»

defer函数嵌套函数执行顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

func main() {
    a := 1
    b := 2
    // The value of calc("10", a, b) needs to be resolved first to be registered with defer.
    defer calc("1", a, calc("10", a, b))
    a = 0
    defer calc("2", a, calc("20", a, b))
    b = 1
}

详解6»

new和make初始化

new会自动用zero value初始化值,比如0,nil,““等,返回指针(指针不是nil,是指向存储地址)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
list := new([]int)
*list = append(*list, 1)
fmt.Println(*list)

stu := new(map[string]int)
*stu = nil
// fmt.Println(*stu)
(*stu)["明晶"] = 12

// listm := make([]int, 0)
// fmt.Println(listm)
// stum := make(map[string]int)
// stum["明晶"] = 12

详解»

struct比较

下面程序编译会通过吗?为什么?

 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
func main() {

	sn1 := struct {
		name string
		age  int
	}{age: 11, name: "qq"}
	sn2 := struct {
		age  int
		name string
	}{age: 11, name: "qq"}

	if sn1 == sn2 {  //变量顺序不同
		fmt.Println("sn1 == sn2")
	}

	sm1 := struct {
		age int
		m   map[string]string
	}{age: 11, m: map[string]string{"a": "1"}}
	sm2 := struct {
		age int
		m   map[string]string
	}{age: 11, m: map[string]string{"a": "1"}}

	if sm1 == sm2 { 
		fmt.Println("sm1 == sm2")
	}
}

  • struct函数变量类型、顺序要一样才能比较

  • slice、map不可以通过==等直接比较,会编译报错

struct containing map[string]string cannot be compared

可以通过reflect.DeepEqual比较

组合重写方法

 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
func main() {
	stu := Student{}
	stu.ShowA()
}

type People struct {
}

func (this *People) ShowA() {
	fmt.Println("show A")
	this.ShowB()
}

func (this *People) ShowB() {
	fmt.Println("show B")
}

type Student struct {
	People
}

func (this *Student) ShowB() {
	fmt.Println("student show B")
}

// 改进
// func (this *Student) ShowA() {
// 	this.People.ShowA()
// 	this.ShowB()
// }

输出:

1
2
show A
show B

golang没有继承,只有组合,Student组合了People的功能,但People下的方法无法知道Student的方法ShowB。这点跟java的继承方法不同. golang的组合有点像俄罗斯套娃,大套娃可以call小套娃call它的方法,但小套娃里面的功能大套娃看不见;而java的继承,和生物继承有些类似,子类继承了父类的所有方法成为自己身体的一部分。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class People {
    public void showA(){
        System.out.println("show A");
        showB();
    }

    public void showB(){
        System.out.println("show B");
    }
}

public void showB(){
  //        super.showB();
  System.out.println("student show B");
}

public class Main {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.showA();
    }
}

上面java代码输出:

1
2
show A
student show B

switch 和 select

select的case操作只能是io语句

  • 没有default语句时,select会一直等待知道某个io可以操作
  • 如果有多个case同时可以运行,会随机选择一个执行

switch的case,只有表达式、类型(boolean-expression or integral type)»

interface方法集

https://golang.google.cn/ref/spec#Method_sets

找找下面代码段问题

 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

import (
	"fmt"
)

type People interface {
	Speak(string) string
}

type Student struct{}

func (stu *Student) Speak(think string) (talk string) {
	if think == "bitch" {
		talk = "You are a good boy"
	} else {
		talk = "hi"
	}
	return
}

func main() {
	var peo People = Student{} 
	think := "bitch"
	fmt.Println(peo.Speak(think))
}

如上例有一种修改方法是将Speak方法改为

1
2
3
4
5
6
7
8
func (stu Stduent) Speak(think string) (talk string) {
	if think == "bitch" {
		talk = "You are a good boy"
	} else {
		talk = "hi"
	}
	return
}

此时声明语句可以接收(t T) and (t *T)两种方法

1
2
var peo People = &Stduent{}
var peo People = Stduent{}

从接收者的角度来看规则

Values Methods Receivers
T (t T)
*T (t T) and (t *T)

对于类型T,它的方法集只包含接收T类型的方法;对于类 *T,它的方法集包含接收T类型的方法和 接收 *T 类型的方法。故上面的Student{} 的方法集没有Speak方法,所以不可以用People接口接收;若改为&Student{},它包含了Speak方法集,则可以。(仅对interface有效)

1
2
3
4
5
6
7
func tell(peo People, sentence string) {
	peo.Speak(sentence)
}
func main() {
		var stu Student = Student{}
		tell(&stu, "bitch") //或 tell(stu, "bitch") T/*T都可以适配T
}

若开头的问题声明,换成下面代码,会有问题吗?没问题,但不是说Student{}的方法集不包含Speak方法吗?没错,但是你这样写编译器会帮你把 peo.Speak(think) 改成 (&peo).Speak(think)

1
2
3
	peo := Student{}
	think := "bitch"
	fmt.Println(peo.Speak(think))

开头问题答案:

1
cannot use (Student literal) (value of type Student) as People value in variable declaration: missing method Speak

interface内部结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type People interface {
	Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func live() People {
	var stu *Student
	return stu
}

func main() {
	if live() == nil {
		fmt.Println("AAAAAAA")
	} else {
		fmt.Println("BBBBBBB")
	}
}

data指向了nil并不代表interface指向了nil,interface{}才是真的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
27
28
29
type eface struct {      //空接口
    _type *_type         //类型信息
    data  unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type iface struct {      //带有方法的接口
    tab  *itab           //存储type信息还有结构实现方法的集合
    data unsafe.Pointer  //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type _type struct {
    size       uintptr  //类型大小
    ptrdata    uintptr  //前缀持有所有指针的内存大小
    hash       uint32   //数据hash值
    tflag      tflag
    align      uint8    //对齐
    fieldalign uint8    //嵌入结构体时的对齐
    kind       uint8    //kind 有些枚举值kind等于0是无效的
    alg        *typeAlg //函数指针数组,类型实现的所有方法
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}
type itab struct {
    inter  *interfacetype  //接口类型
    _type  *_type          //结构类型
    link   *itab
    bad    int32
    inhash int32
    fun    [1]uintptr      //可变大小 方法集合
}

Type Alias(类型别名)

1
2
3
4
5
6
7
8
func main()  {
    type MyInt1 int
    type MyInt2 = int
    var i int =9
    var i1 MyInt1 = i
    var i2 MyInt2 = i
    fmt.Println(i1,i2)
}

MyInt1是基于int定义的新类型,MyInt2是int的别名。

闭包

闭包引用相同变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func main() {
	a, b := test(10)
	a()
	b()
}

func test(x int) (func(), func()) {
	return func() {
			fmt.Println(x)
			x += 10
		}, func() {
			fmt.Println(x)
		}
}

闭包变量延迟

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func main() {
	funcs := test(10)
	for _, f := range funcs {
		f()
	}
}

func test(x int) []func() {
	var funcs []func()
	for i := 0; i < 2; i++ {
		funcs = append(funcs, func() {
			fmt.Println(i)
		})
	}

	return funcs
}

Rune处理utf-8字符串个数神器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func Utf8Index(str, substr string) int {
	asciiPos := strings.Index(str, substr)
	if asciiPos == -1 || asciiPos == 0 {
		return asciiPos
	}

	totalSize := 0
	pos := 0
	reader := strings.NewReader(str)
	for _, size, err := reader.ReadRune(); err == nil; _, size, err = reader.ReadRune() {
		totalSize += size
		pos++
		if totalSize == asciiPos {
			return pos
		}
	}
	return pos
}

nil值

对于 nil 的 map、channel,我们可以简单的把它看成只读对象,写入会 panic。

nil的slice,可读可写

1
2
3
4
5
6
7
8
9
var m map[string]string //nil
m["name"] = "zzy" // nil error
e, _ := m["hello"] 
fmt.Println(len(e)) // ""


var s []int //nil
fmt.Println(len(s)) //0
s = append(s, 1)

close chan

Receive on a closed channel will return the zero value for the channel’s type without blocking:

1
2
3
4
5
6
7
package main
import "fmt"
func main() {
        ch := make(chan int)
        close(ch)
        fmt.Println(<-ch)
}

The executing result is like below:

1
0

详情»

nsq的topic exitChan利用了这一特性来忽略操作

Recover

发生panic,若不处理,整个Go程序会退出。为了让程序继续执行,Go有一个类似 Java 的 Throw…catch 机制来处理错误 —— Recover。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func foo(a int) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("捕捉到错误: %s\n", r)
		}
	}()
	b := 0
	a = a / b
}
func main() {
	go foo(1)
	time.Sleep(time.Second)
	fmt.Println("正常退出")
}

输出:

1
2
3
$ go run main.go
捕捉到错误: runtime error: integer divide by zero
正常退出

但 recover 只对当前函数的错误有用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func foo(a int) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("捕捉到错误: %s\n", r)
		}
		b := 0
		a = a / b
	}()
}
func main() {
	go foo(1)
	time.Sleep(time.Second)
	fmt.Println("正常退出")
}

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ go run main.go
panic: runtime error: integer divide by zero

goroutine 6 [running]:
main.foo.func1(0xc00004c7d8)
        /Users/cmj/workspace/Go/src/hello/main.go:76 +0xa2
main.foo(0x1)
        /Users/cmj/workspace/Go/src/hello/main.go:78 +0x53
created by main.main
        /Users/cmj/workspace/Go/src/hello/main.go:80 +0x42
exit status 2

查看内存逃逸

内存逃逸:栈上的对象逃逸到了堆上

1
go build -gcflags '-m -m' main.go

逃逸情况

  • 占用的内存太大,比如 make([]int, 10000000)

    1
    2
    3
    4
    
    // Large objects (> 32 kB) are allocated straight from the heap.
    func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
      ...
    }
    
  • 函数返回指针,无法推断对象的生命周期

  • 因不确定长度或动态扩展,需要在运行时重新分配地址,可预算空间

  • 运行时不确定的类型也会逃逸,比如interface

分配在堆上的缺点:堆上的对象使用完后需要 GC

https://studygolang.com/articles/22875

性能分析工具 pprof

追踪GC

可以和pprof配合使用

1
GODEBUG=gctrace=1 go run app.go

slice遍历可以不取值

1
2
3
nums := []int{1, 2, 3}
for range nums {
}

参考

https://learnku.com/go/t/23460/bit-operation-of-go

https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function