查看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项。当然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 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()
// }
 | 
 
输出:
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:
详情»
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