tour of go day3

go tour 看完了,接下来整理Effective go

类型参数(模板类)

在函数名后或者类型名后加中括号可以为其指定类型参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Index 返回 x 在 s 中的下标,未找到则返回 -1。
func Index[T comparable](s []T, x T) int {
for i, v := range s {
// v 和 x 的类型为 T,它拥有 comparable 可比较的约束,
// 因此我们可以使用 ==。
if v == x {
return i
}
}
return -1
}

func main() {
// Index 可以在整数切片上使用
si := []int{10, 20, 15, -10}
fmt.Println(Index(si, 15))

// Index 也可以在字符串切片上使用
ss := []string{"foo", "bar", "baz"}
fmt.Println(Index(ss, "hello"))
}
1
2
3
4
type List[T any] struct {
next *List[T]
val T
}

协程

使用go语句来启动一个新的协程

1
2
3
4
5
6
7
8
9
10
11
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}

func main() {
go say("world")
say("hello")
}

启动协程时,参数的求值在当前协程中,函数的执行在新协程中,不同协程共享同样的内存空间

信道

信道用于在协程中进行通信,相比sync库而言信道是一种更轻量化的同步手段,信道分为带缓冲和无缓冲两种
信道由make(chan T,size)语句创建,当size为0是就是无缓冲的信道
对于无缓冲信道的发送端而言,在写入数据后会先检查是否有其他协程在等待数据,如果有则直接转移数据并继续执行,否则阻塞,对于接收端而言,在接收到数据前都会阻塞,接收到后执行,此处的发送和接收都是单次事件,即接收/发送一个数据(比如一个值)后就会解除阻塞
对于带缓冲信道的发送端,如果缓存已满则阻塞,否则不断往缓存中写数据,对于接收端,如果缓存中有数据则从中间取一个数据并继续执行,否则阻塞

range/close

close语句用于永久关闭一个信道,被关闭的信道,被关闭的信道不能写入新数据,缓冲区中已有的数据读取完后就只能读取到零值,可以向<-语句添加第二个参数检查信道是否已经关闭v, ok := <-ch

for range循环可以用于不断从信道中接收值,直到信道关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}

func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}

关于close有两个原则,一是信道并不总是需要关闭,只有当我们想通知接收方停止接收数据时我们才需要关闭信道,二是信道总是应该由发送者关闭,因为向已关闭的信道发送数据会导致panic,这里的信号其实是单向传递的

select

select可以用于等待多个协程,其会阻塞到某个分支可以继续执行,有点类似协程版的switch,如果有多个分支可以继续执行则会 随机 选择一个分支执行(go tour是这么说的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}

func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}

select也支持default分支,当没有分支能执行但又不想阻塞时就可以添加default分支执行

Mutex

sync.Mutex是go中最基本的互斥锁,当我们只想进行同步而不转移数据时可以利用MutexLockUnlock方法手动对协程上锁,一个技巧是使用defer调用Unlock来保证协程一定会被解锁

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
// SafeCounter 是并发安全的
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}

// Inc 对给定键的计数加一
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// 锁定使得一次只有一个 Go 协程可以访问映射 c.v。
c.v[key]++
c.mu.Unlock()
}

// Value 返回给定键的计数的当前值。
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// 锁定使得一次只有一个 Go 协程可以访问映射 c.v。
defer c.mu.Unlock()
return c.v[key]
}

func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}

time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
Author

SGSG

Posted on

2025-07-29

Updated on

2025-07-29

Licensed under