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 func Index [T comparable ](s []T, x T) int { for i, v := range s { if v == x { return i } } return -1 } func main () { si := []int {10 , 20 , 15 , -10 } fmt.Println(Index(si, 15 )) 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中最基本的互斥锁,当我们只想进行同步而不转移数据时可以利用Mutex
的Lock
和Unlock
方法手动对协程上锁,一个技巧是使用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 type SafeCounter struct { mu sync.Mutex v map [string ]int } func (c *SafeCounter) Inc(key string ) { c.mu.Lock() c.v[key]++ c.mu.Unlock() } func (c *SafeCounter) Value(key string ) int { c.mu.Lock() 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" )) }