Golang学习笔记
常用占位符
https://www.jianshu.com/p/66aaf908045e
问题记录
1. 接口
1.1 sort.Reverse如何实现逆排序?
golang: sort.Sort(sort.Reverse(n))是怎么进行逆排序? - soolaugust的回答 - 知乎 https://www.zhihu.com/question/360515958/answer/932843144
2. Goroutine、Channel
2.1 all goroutines are asleep - deadlock!
func main() {
chnl := make(chan string, 10)
chnl <- "a"
chnl <- "b"
chnl <- "c"
chnl <- "d"
chnl <- "e"
flag := make(chan int, 1)
go func() {
for s := range chnl {
fmt.Println("1", s)
}
flag <- 1
fmt.Println("flag1done")
}()
<-flag
go func() {
for s := range chnl {
fmt.Println("2", s)
}
flag <- 1
fmt.Println("flag2done")
}()
<-flag
fmt.Println("all done")
}
输出如下:
1 a
1 b
1 c
1 d
1 e
fatal error: all goroutines are asleep - deadlock!
注意到flag1done
甚至没有输出,说明第一个协程中的for循环没有结束,导致flag<-1没有执行。而flag中一直没有写入,主协程中的读取就会阻塞,导致死锁。
原因:range遍历Channel时依次从Channel接收数据,当Channel被关闭且没有数据可接收时跳出遍历。所以,只有先主动关闭chnl来跳出range循环,才可以让第一个协程往flag写数据。
只需要在一开始往chnl写完数据后close(chnl)
就可以解决了。现在输出如下:
1 a
1 b
1 c
1 d
1 e
flag1done
flag2done
all done
在上面的代码我让第一个协程完成任务后再发起第二个协程,现在做如下改动,让两个协程并发遍历chnl
flag := make(chan int, 1)
go func() {
for s := range chnl {
fmt.Println("1", s)
}
// flag <- 1
fmt.Println("flag1done")
}()
// <-flag
go func() {
for s := range chnl {
fmt.Println("2", s)
}
flag <- 1
fmt.Println("flag2done")
}()
<-flag
fmt.Println("all done")
此时输出如下:
1 b
1 c
1 d
1 e
flag1done
2 a
flag2done
all done
也说明协程之间通过Channel读取数据可以不用考虑线程安全性,因为一个数据被读取出来后,管道里也会对应地少一个数据。(对吗?)
2.2 并发的退出
读go圣经8.9章“并发的退出”的时候,有些一开始搞混的地方,这里做记录。
Channel广播机制用来通知goroutine关闭:
回忆一下我们关闭了一个channel并且被消费掉了所有已发送的值,操作channel之后的代码可以立即被执行,并且会产生零值。我们可以将这个机制扩展一下,来作为我们的广播机制:不要向channel发送值,而是用关闭一个channel来进行广播。
var done = make(chan struct{})
func cancelled() bool {
select {
case <-done:
return true
default:
return false
}
}
这里的done我们并不往里面写值,只是在需要通知goroutine关闭的时候close(done)
,这样cancelled
函数就会返回true了。在goroutine里可以通过cancelled函数的返回值判断是否结束工作。
3. 共享变量
数据竞争的定义:
数据竞争会在两个以上的goroutine并发访问相同的变量且至少其中一个为写操作时发生。
4. Goroutines和线程
从动态栈、Goroutine调度、GOMAXPROCS、有无ID号四方面,详见《Go语言圣经》9.8章:https://docs.hacknode.org/gopl-zh/ch9/ch9-08.html