1 分支

  • if-else
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 基本的 if-else
if condition {
// 代码块
} else {
// 代码块
}

// if 可以包含一个初始化语句
if result := someFunction(); result > 0 {
// 使用 result
}

// if-else if-else 链
if condition1 {
// 代码块
} else if condition2 {
// 代码块
} else {
// 代码块
}
  • switch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// switch 语句
switch value {
case 1:
// 代码块
case 2, 3, 4:
// 代码块
default:
// 代码块
}

// switch 无条件 - 类似 if-else 链
switch {
case condition1:
// 代码块
case condition2:
// 代码块
default:
// 代码块
}

2 循环

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
for i := 0; i < N; i++ {
// 代码块
}

for condition {
// 代码块
}

// 无限循环
for {
// 代码块
if condition {
break // 跳出循环
}
if condition2 {
continue // 继续下一次循环
}
}

// 1. int[n] int[]
for index, value := range array {
// 使用 index 和 value
}

// 2. map
for key, value := range map {
// 使用 key 和 value
}

// 3. channel
for item := range channel {
// 使用 item
}

3 结构体

1
2
3
4
5
6
7
8
9
10
11
type T struct {
// ...
}

func (t T) F1(...) ... {
// ...
}

func (t *T) F2(...) ... {
// ...
}
1
2
3
4
5
6
7
t := T{...}
t.F1(...) // 调用者(值类型) 调用 接收者(值类型)的方法
t.F2(...) // 调用者(值类型) 调用 接收者(指针类型)的方法

tt := &T{...}
tt.F1(...) // 调用者(指针类型) 调用 接收者(值类型)的方法
tt.F2(...) // 调用者(指针类型) 调用 接收者(指针类型)的方法
样例 调用者 接收者 说明
t.F1(...) 值类型调用者t := T{...} 值类型接收者func (t T) F1() ...{...}
t.F2(...) 值类型调用者t := T{...} 指针类型接收者func (t *T) F2() ...{...} $$\Rightarrow$$(&t).F2(…)
tt.F1(...) 指针类型调用者tt := &T{...} 值类型接收者func (t T) F1() ...{...} $$\Rightarrow$$(*t).F1(…)
tt.F2(...) 指针类型调用者tt := &T{...} 指针类型接收者func (t *T) F2() ...{...}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
t := T{n: 2024}
t.F1()
fmt.Println(t.n)
t.F2()
fmt.Println(t.n)

tt := &T{n: 2024}
tt.F1()
fmt.Println(tt.n)
tt.F2()
fmt.Println(tt.n)
}

2024
2025
2024
2025

4 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type I interface {
F1()
F2()
}

type T struct {
// ...
}

func (t T) F1(...) ... {
// ...
}

func (t *T) F2(...) ... {
// ...
}
  • T 类型的方法集包含所有 receiver 为 T 的方法
  • *T 类型的方法集包含所有 receiver 为 *T 和 T 的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
t1 := T{n: 2024}
var i1 I = &t1
// i1 is the interface belonging to *T
i1.F1()
fmt.Println(t1.n)
i1.F2()
fmt.Println(t1.n)

t2 := T{n: 2024}
var i2 I = t2
// i2 is the interface belonging to T
i2.F1()
fmt.Println(t2.n)
i2.F2()
fmt.Println(t2.n)
}

interface{} 表示任何类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Retriever interface {
Get(url string) string
}

type Poster interface {
Post(url string, form map[string]string) string
}

// use Retriever
func get(r Retriever) string {
return r.Get(url)
}

// use Poster
func post(p Poster) string {
return p.Post(url, map[string]string{
"name": " solisamicus",
"course": "golang",
})
}
1
2
3
4
5
6
7
8
9
10
11
12
type RetrieverPoster interface {
Retriever
Poster
}

// use RetrieverPoster
func session(rp RetrieverPoster) string {
_ = rp.Post(url, map[string]string{
"content": "update news",
}) // ok
return rp.Get(url)
}

假设 mock.RetrieverPoster 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package mock

type RP struct {
Contents string
}

func (rp *RP) Post(url string, form map[string]string) string {
rp.Contents = form["content"]
return "ok"
}

func (rp *RP) Get(url string) string {
return rp.Contents
}

func NewRP(contents string) *RP {
return &RP{
Contents: contents,
}
}

演示:

1
2
3
4
5
6
7
8
9
const url = "xxx"

func main() {
var rp RetrieverPoster = mock.NewRP("mock news") // &mock.RP{Contents: "mock news"}
// 1. 作为 Retriever 使用 (调用 Get)
// 2. 作为 Poster 使用 (调用 Post)
// 3. 作为 RetrieverPoster 使用 (同时调用 Get 和 Post)
fmt.Println(session(rp))
}

update news

5 函数

6 错误

error vs panic

7 测试

1
2
3
4
5
6
7
func Add(a, b int) int {
return a + b
}

func Multiply(a, b int) int {
return a * b
}
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
func TestAdd(t *testing.T) {
tests := []struct {
a, b, expected int
}{
{1, 2, 3},
{5, 5, 10},
{-1, 1, 0},
{-3, -4, -7},
}

for _, tt := range tests {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("expected %d, but got %d", tt.expected, result)
}
}
}

func TestMultiply(t *testing.T) {
tests := []struct {
a, b, expected int
}{
{1, 2, 2},
{3, 3, 9},
{-1, 5, -5},
{-3, -4, 12},
}

for _, tt := range tests {
result := Multiply(tt.a, tt.b)
if result != tt.expected {
t.Errorf("expected %d, but got %d", tt.expected, result)
}
}
}

func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}

func BenchmarkMultiply(b *testing.B) {
for i := 0; i < b.N; i++ {
Multiply(3, 4)
}
}

单元测试:

1
2
$ go test
ok github.com/solisamicus/learngo/test 0.128s

代码覆盖率:

1
2
3
4
$ go test -coverprofile=coverage.out .
ok github.com/solisamicus/learngo/test 6.203s coverage: 100.0% of statements

$ go tool cover -html=coverage.out

性能测试:

1
2
3
4
5
6
7
8
9
$ go test -bench .
goos: windows
goarch: amd64
pkg: github.com/solisamicus/learngo/test
cpu: AMD Ryzen 7 8845H w/ Radeon 780M Graphics
BenchmarkAdd-16 1000000000 0.2257 ns/op
BenchmarkMultiply-16 1000000000 0.2189 ns/op
PASS
ok github.com/solisamicus/learngo/test 0.608s

pprof

1
2
3
4
5
6
7
8
9
$ go test -bench . -cpuprofile cpu.pprof -memprofile mem.pprof
goos: windows
goarch: amd64
pkg: github.com/solisamicus/learngo/test
cpu: AMD Ryzen 7 8845H w/ Radeon 780M Graphics
BenchmarkAdd-16 1000000000 0.2273 ns/op
BenchmarkMultiply-16 1000000000 0.2225 ns/op
PASS
ok github.com/solisamicus/learngo/test 6.858s
  • -cpuprofile cpu.pprof:生成 CPU 性能分析文件 cpu.pprof
  • -memprofile mem.pprof:生成内存分配分析文件 mem.pprof
1
2
3
4
5
6
7
8
$ go tool pprof cpu.pprof
File: test.test.exe
Build ID: C:\Users\SOLISA~1\AppData\Local\Temp\go-build1599404661\b001\test.test.exe2024-12-18 15:07:10.6553213 +0800 CST
Type: cpu
Time: Dec 18, 2024 at 3:07pm (CST)
Duration: 620.56ms, Total samples = 490ms (78.96%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) web

(pprof) web

failed to execute dot. Is Graphviz installed? Error: exec: “dot”: executable file not found in %PATH%

pprof 使用 Graphviz 工具生成图形化的报告

https://graphviz.org/download/

8 并发

goroutine

  1. "轻量级"线程
  2. 非抢占式多任务处理,由协程主动交出控制权
  3. 编译器/解释器/虚拟机层面的多任务
  4. 多个协程可能在一个或多个线程上运行

8.1 通道

example 1

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
func doWork(id int, c <-chan int) {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
}
}

func createWorker(id int) chan int {
c := make(chan int)
go doWork(id, c)
return c
}

const n = 5

func main() {
var workers [n]chan int

for i := 0; i < n; i++ {
workers[i] = createWorker(i)
}

for i := 0; i < n; i++ {
workers[i] <- 'a' + i
}

time.Sleep(time.Second)
}

Worker 2 received c

Worker 3 received d

Worker 0 received a

Worker 4 received e

Worker 1 received b

时间点 主程序 Worker goroutine
t1 go worker(0, c0) 启动 worker 0, 等待接收数据
t2 go worker(1, c1) 启动 worker 1, 等待接收数据
t3 go worker(2, c2) 启动 worker 2, 等待接收数据
t4 go worker(3, c3) 启动 worker 3, 等待接收数据
t5 go worker(4, c4) 启动 worker 4, 等待接收数据
t6 c0 <- 'a' 向 worker 0 发送数据
t7 c1 <- 'b' 向 worker 1 发送数据
t8 c2 <- 'c' 向 worker 2 发送数据
t9 c3 <- 'd' 向 worker 3 发送数据
t10 c4 <- 'e' 向 worker 4 发送数据

example 2

并行启动,串行结束:

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

import (
"fmt"
"time"
)

type worker struct {
c chan int
done chan bool
}

func doWork(id int, c <-chan int, done chan<- bool) {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
done <- true
}
}

func createWorker(id int) worker {
w := worker{
c: make(chan int),
done: make(chan bool),
}
go doWork(id, w.c, w.done)
return w
}

const n = 5

func main() {
var workers [n]worker

for i := 0; i < n; i++ {
workers[i] = createWorker(i)
}

for i := 0; i < n; i++ {
workers[i].c <- 'a' + i
<-workers[i].done
}

time.Sleep(time.Second)
}

Worker 0 received a

Worker 1 received b

Worker 2 received c

Worker 3 received d

Worker 4 received e

并行启动,并行结束:

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
type worker struct {
c chan int
done chan bool
}

func doWork(id int, c <-chan int, done chan<- bool) {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
done <- true
}
}

func createWorker(id int) worker {
w := worker{
c: make(chan int),
done: make(chan bool),
}
go doWork(id, w.c, w.done)
return w
}

const n = 5

func main() {
var workers [n]worker

for i := 0; i < n; i++ {
workers[i] = createWorker(i)
}

for i := 0; i < n; i++ {
workers[i].c <- 'a' + i
}

for i := 0; i < n; i++ {
<-workers[i].done
}

time.Sleep(time.Second)
}

Worker 0 received a

Worker 4 received e

Worker 3 received d

Worker 2 received c

Worker 1 received b

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
type worker struct {
c chan int
done chan bool
}

func doWork(id int, c <-chan int, done chan<- bool) {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
done <- true
}
}

func createWorker(id int) worker {
w := worker{
c: make(chan int),
done: make(chan bool),
}
go doWork(id, w.c, w.done)
return w
}

const n = 5

func main() {
var workers [n]worker

for i := 0; i < n; i++ {
workers[i] = createWorker(i)
}

for i := 0; i < n; i++ {
workers[i].c <- 'a' + i
}

for i := 0; i < n; i++ {
workers[i].c <- 'A' + i
}

for i := 0; i < n; i++ {
<-workers[i].done
<-workers[i].done
}

time.Sleep(time.Second)
}

Worker 0 received a

Worker 2 received c

Worker 1 received b

Worker 3 received d

Worker 4 received e

fatal error: all goroutines are asleep - deadlock!

发生阻塞!

solution 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// original version
func doWork(id int, c <-chan int, done chan<- bool) {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
go func() { done <- true }()
}
}

// modified version
func doWork(id int, c <-chan int, done chan<- bool) {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
go func() { done <- true }()
}
}

solution 2

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
func main() {
var workers [n]worker

for i := 0; i < n; i++ {
workers[i] = createWorker(i)
}

for i := 0; i < n; i++ {
workers[i].c <- 'a' + i
}

for i := 0; i < n; i++ {
<-workers[i].done
}

for i := 0; i < n; i++ {
workers[i].c <- 'A' + i
}

for i := 0; i < n; i++ {
<-workers[i].done
}

time.Sleep(time.Second)
}

example 3

直接使用 sync.WaitGroup,适合简单并发控制。

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
func doWork(id int, c <-chan int, wg *sync.WaitGroup) {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
wg.Done()
}
}

type worker struct {
c chan int
wg *sync.WaitGroup
}

func createWorker(id int, wg *sync.WaitGroup) worker {
w := worker{
c: make(chan int),
wg: wg,
}
go doWork(id, w.c, w.wg)
return w
}

const n = 5

func main() {
var wg sync.WaitGroup
wg.Add(10)

var workers [n]worker

for i := 0; i < n; i++ {
workers[i] = createWorker(i, &wg)
}

for i := 0; i < n; i++ {
workers[i].c <- 'a' + i
}

for i := 0; i < n; i++ {
workers[i].c <- 'A' + i
}

wg.Wait()
}

Worker 3 received d

Worker 0 received a

Worker 0 received A

Worker 4 received e

Worker 1 received b

Worker 1 received B

Worker 2 received c

Worker 2 received C

Worker 4 received E

Worker 3 received D

通过回调方式传递 done 函数,增加了灵活性,但也稍微增加了代码的复杂度。

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
func doWork(id int, w worker) {
for n := range w.c {
fmt.Printf("Worker %d received %c\n", id, n)
w.done()
}
}

type worker struct {
c chan int
done func()
}

func createWorker(id int, wg *sync.WaitGroup) worker {
w := worker{
c: make(chan int),
done: func() {
wg.Done()
},
}
go doWork(id, w)
return w
}

const n = 5

func main() {
var wg sync.WaitGroup


wg.Add(10)

var workers [n]worker

for i := 0; i < n; i++ {
workers[i] = createWorker(i, &wg)
}

for i := 0; i < n; i++ {
workers[i].c <- 'a' + i
}

for i := 0; i < n; i++ {
workers[i].c <- 'A' + i
}

wg.Wait()
}

Worker 3 received d

Worker 0 received a

Worker 0 received A

Worker 4 received e

Worker 1 received b

Worker 1 received B

Worker 2 received c

Worker 2 received C

Worker 4 received E

Worker 3 received D

example 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func doWork(id int, c <-chan int) {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
}
}

func bufferedChannel() {
c := make(chan int, 3)
go doWork(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
//close(c)
}

func main() {
bufferedChannel()

time.Sleep(time.Microsecond)
}

注释close(c)

Worker 0 received a

Worker 0 received b

Worker 0 received c

Worker 0 received d

不注释close(c)

Worker 0 received a

Worker 0 received b

Worker 0 received c

Worker 0 received d

Worker 0 received

Worker 0 received

Worker 0 received

Worker 0 received

Worker 0 received

  • close(c) :worker goroutine 阻塞在接收操作上,直到有新的数据或通道关闭。
  • close(c) :当通道被关闭后,worker goroutine 仍然尝试接收数据,但因为通道已关闭且没有更多数据,<-c 会返回通道的零值(对于 int0),导致不停地打印 Worker 0 received

配合 close(),其他写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// range
func doWork(id int, c chan int) {
for n := range c { // When c is closed and has no data, range automatically exits.
fmt.Printf("Worker %d received %c\n", id, n)
}
}

// if-ok
func doWork(id int, c chan int) {
for {
n, ok := <-c // Explicitly check if the channel is closed
if !ok { // If c is closed and has no data, exit the loop
break
}
fmt.Printf("Worker %d received %c\n", id, n)
}
}

除此之外,还有:

  • sync.Mutex
  • sync.Cond

8.2 并发模式

生产者

1
2
3
4
5
6
7
8
9
10
11
12
func msgGen(name string) chan string {
c := make(chan string)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) // execution of mock business
c <- fmt.Sprintf("service %s: message %d", name, i)
i++
}
}()
return c
}

消费者

单个消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
func fanInByLoop(chs ...chan string) chan string {
m := make(chan string)
for _, cs := range chs {
go func(cs chan string) {
var s string
for {
s = <-cs
m <- s
}
}(cs)
}
return m
}
  • 多个消费者:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func fanInBySelect(m1, m2, m3 chan string) chan string {
m := make(chan string)
go func() {
var s string
for {
select {
case s = <-m1:
m <- s
case s = <-m2:
m <- s
case s = <-m3:
m <- s
}
}
}()
return m
}

比较:

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
func fanInByLoop(chs ...chan string) chan string {
m := make(chan string)
for _, cs := range chs {
go func(cs chan string) {
for {
s := <-cs
m <- s
}
}(cs)
}
return m
}

func fanInByLoop(chs ...chan string) chan string {
m := make(chan string)
for _, cs := range chs {
var s string
go func(cs chan string) {
for {
s = <-cs
m <- s
}
}(cs)
}
return m
}

func fanInByLoop(chs ...chan string) chan string {
m := make(chan string)
for _, cs := range chs {
go func(cs chan string) {
var s string
for {
s = <-cs
m <- s
}
}(cs)
}
return m
}
  • 第一个版本(可接受):goroutine 内部 + for 循环内
  • 第二个版本(不推荐):在外部声明,但在 goroutine 外,可能有竞态条件
  • 第三个版本(推荐):goroutine 内部 + for 循环外

8.3 任务控制

8.3.1 非阻塞等待

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
func msgGen(name string) chan string {
c := make(chan string)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) // execution of mock business
c <- fmt.Sprintf("service %s: message %d", name, i)
i++
}
}()
return c
}

func nonBlockWait(c chan string) (string, bool) {
select {
case s := <-c:
return s, true
default:
return "", false
}
}

func main() {
m1 := msgGen("service1")
m2 := msgGen("service2")
for {
fmt.Println(<-m1)
if s, ok := nonBlockWait(m2); ok {
fmt.Println(s)
} else {
fmt.Println("no message received from m2")
}
}
}

service service1: message 0

service service2: message 0

service service1: message 1

no message received from m2

service service1: message 2

service service2: message 1

service service1: message 3

no message received from m2

service service1: message 4

service service2: message 2

service service1: message 5

no message received from m2

service service1: message 6

service service2: message 3

service service1: message 7

no message received from m2

service service1: message 8

8.3.2 超时机制

1
time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) // execution of mock business

延迟时间是随机的,范围是从 0 毫秒到 2000 毫秒之间。

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
func msgGen(name string) chan string {
c := make(chan string)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) // execution of mock business
c <- fmt.Sprintf("service %s: message %d", name, i)
i++
}
}()
return c
}

func timeoutWait(c chan string, timeout time.Duration) (string, bool) {
select {
case s := <-c:
return s, true
case <-time.After(timeout):
return "", false
}
}

func main() {
m := msgGen("service")
for {
if s, ok := timeoutWait(m, 1*time.Second); ok {
fmt.Println(s)
} else {
fmt.Println("timeout")
}
}
}

timeout

service service: message 0

service service: message 1

timeout

service service: message 2

service service: message 3

service service: message 4

service service: message 5

service service: message 6

service service: message 7

timeout

service service: message 8

timeout

8.3.3 优雅退出

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 msgGen(name string, done chan struct{}) chan string {
c := make(chan string)
go func() {
i := 0
for {
select {
case <-time.After(time.Duration(rand.Intn(2000)) * time.Millisecond):
c <- fmt.Sprintf("service %s: message %d", name, i)
case <-done:
fmt.Println("cleaning up")
time.Sleep(2 * time.Second)
fmt.Println("done")
done <- struct{}{}
return
}
i++
}
}()
return c
}

func main() {
done := make(chan struct{})
m := msgGen("service", done)
for i := 0; i < 10; i++ {
fmt.Println(<-m)
}
done <- struct{}{}
<-done
}

service service: message 0

service service: message 1

service service: message 2

service service: message 3

service service: message 4

service service: message 5

service service: message 6

service service: message 7

service service: message 8

service service: message 9

cleaning up

done