go basics

go-basics

string

fields&maps

func WordCount(s string) map[string]int {
	cntMap := make(map[string]int)
	strs := strings.Fields(s)
	for _, s := range strs {
		i := cntMap[s]
		cntMap[s] = i + 1
	}

	return cntMap
}
  • string按空格拆分成列表
strs := strings.Fields(s)
  • 遍历list

for _, s := range strs {
 // xxx
}
  • 注意: 自加不能赋值(与Java有区别)
// 如下是非法的. 
y := x++
// 
x++
y = x
  • append

map

package core

import "log"

type NUMANodeMap map[int]string

func iter() {
	nodeMap := NUMANodeMap{
		1: "aa",
		2: "bb",
	}
	// 遍历key
	for n := range nodeMap {
		// n 代表key
		log.Println(n)
		// nodeMap[n] 获取value
		log.Println(nodeMap[n])
	}

	for k, v := range nodeMap {
		// k 代表key
		// v 代表value
		log.Println(k, v)
	}

	// map中增加kv
	nodeMap[3] = "cc"

	// map中移除kv
	delete(nodeMap, 1)
	log.Println(nodeMap)
}

struct

参见: Create, initialize and compare structs · YourBasic Go

简单struct初始化

func TestVertex_Init(t *testing.T) {
	// 单行初始化, 末尾不用 逗号
	v := Vertex{23.2, 22.3}
	log.Println(v)

	// 多行初始化, 末尾需要 逗号
	vertex := Vertex{
		23.2,
		22.3,
	}
	log.Println(vertex)

	// 部分初始化, 需要指定 name, 末尾需要 逗号
	vertex2 := Vertex{
		X: 1.2,
	}
	log.Println(vertex2)

	var vertexP *Vertex
	vertexP = new(Vertex)
	vertexP.X = 1.2

	log.Println(vertexP)
	log.Println(*vertexP)
	assert.Equal(t, *vertexP, vertex2)
	assert.Equal(t, vertexP, &vertex2)
	assert.False(t, vertexP == &vertex2)

	vertexP2 := &Vertex{
		1.2,
		2.2,
	}
	log.Println(vertexP2)

	vertexP3 := &Vertex{
		Y: 1.2,
	}
	log.Println(vertexP3)
}

复杂struct初始化

需要指定内部的struct类型(如下例子中的 Vertext )

func TestTriangle_Init(t *testing.T) {
	v1 := Vertex{1.2, 2.2}

	triangle := Triangle{
		v1,
		Vertex{2, 3},
		Vertex{3, 4},
	}

	log.Println(triangle)
}

struct embedding

如下定义的结构代表啥意思? 为啥第一个 *scheduleroptions.Options 没有filedName呢?

// Options has all the params needed to run a Scheduler
type Options struct {
	*scheduleroptions.Options
	CombinedInsecureServing *CombinedInsecureServingOptions
}

实际上使用了: Go by Example: Struct Embedding 方式, 即类似Java的继承

pointer

方法定义中, Pointer Receiver 与 普通Receiver, 如下 Scale 与 Scale2, 实际上Scale2是没有生效的, 是没有修改原来的值的.

所以

  • PointerReceiver: 类似Java类中的普通方法
  • 普通Receiver: 类似Java类中的Static方法
type Vertex struct {
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func (v Vertex) Scale2(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

goroutine

如何获取到goroutine的ID?

参见: https://stackoverflow.com/questions/75361134/how-can-i-get-a-goroutines-runtime-id

故意不让我们获取到.

注意: 一个goroutine crash, 会导致整个go进程挂掉. 因此需要保护,

go func()

go func() 新建的协程竟然可以直接引用局部变量(而不是通过传参方式). 所以是不是很容易把线程不安全的对象泄露?

func localVar() {
	var myVar = "hello"
	//方式1: 临时创建 匿名方法的 routine
	go func() {
		myVar = "Nice"
	}()
	time.Sleep(1 * time.Second)
	log.Println(myVar)

	//方式2: 直接在方法调用前增加 go 关键词
	go localVar2()
}

func localVar2() {
	log.Println("localVar2")
}

go func/chan sample

在k8s代码中, 看到如下代码, 代表啥意思?

func SetupSignalHandler() <-chan struct{} {
	return SetupSignalContext().Done()
}

因此通常的使用方式是:

 ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
	stopCh := server.SetupSignalHandler()
	<-stopCh
	cancel()
}()

routine tree

context

How To Use Contexts in Go | DigitalOcean

channel


var msgChan = make(chan string)

func sayHello() {
	log.Println("sayHello started")

	go func() {
		msgChan <- "ping"
		log.Println("ping sent")
		msgChan <- "pong"
		log.Println("pong sent")
	}()

	time.Sleep(3 * time.Second)

	msg := <-msgChan
	log.Println(msg)
	msg = <-msgChan
	log.Println(msg)

	log.Println("done")
}

普通channel

var msgChan = make(chan string)

普通channel: 只有在收到receive的时候(即receive注册之后), 才会真正send出去. 否则send会一直阻塞.

By default channels are unbuffered, meaning that they will only accept sends (chan <-) if there is a corresponding receive (<- chan) ready to receive the sent value.

即类似Java中的synchronirousQueue, 必须消费者注册/开始消费的时候, 生产者才能执行.

2023/12/05 17:41:29 sayHello started
2023/12/05 17:41:32 ping sent
2023/12/05 17:41:32 ping
2023/12/05 17:41:32 pong sent
2023/12/05 17:41:32 pong
2023/12/05 17:41:32 done

buffered channel

var msgChan = make(chan string, 2)
  • 运行结果如下, 生产者可以直接放进去, 而不用等消费者注册.
2023/12/05 17:39:34 sayHello started
2023/12/05 17:39:34 ping sent
2023/12/05 17:39:34 pong sent
2023/12/05 17:39:37 ping
2023/12/05 17:39:37 pong
2023/12/05 17:39:37 done
  • 如果生产者放入的size大于buffer, 则多余部分会一直卡住. 直到buffer中被消费了1个, 则可以放入1个.

channel方向

作为方法参数时, 可以指定channel方向, 这样防止

只写: chan<-
只读: <-chan
// 只写的channel
// 如果改成 (done <-chan bool) 则会编译错误. 
func worker(done chan<- bool) {
	log.Println("working...")
	time.Sleep(time.Second)
	log.Println("done...")
	done <- true
}

panic&recover

如何手动触发panic: panic("test")

mod

go get/go install

go mod download

git上checkout下来的项目, 可以直接使用该命令, 把依赖包都下载下来.

本地如果有多个go mod工程, 如何进行相互依赖?

  1. 目前看只能使用 replace 的方式. 感觉不太方便.
  2. 否则就只能代码push到地址中, 使用 go mod download 进行下载. 太不方便.
  3. 为啥不能像maven一样, 可以先安装到本地. 然后其他模块依赖本地的即可.

other

  1. 方法名是包级别的. 即一个包下所有的.go文件, 方法不能同名. 而java中是类级别(文件级别)

  2. goland不支持go module, 导致依赖无法下载/跳转, 需要手动enable下:

Untitled

Untitled