網站首頁 編程語言 正文
用var定義channel且不make
wg := sync.WaitGroup{} var ch chan string read := func() { ?? ?fmt.Println("reading") ?? ?s := <-ch ?? ?fmt.Println("read:", s) ?? ?wg.Done() } write := func() { ?? ?fmt.Println("writing") ?? ?s := "t" ?? ?ch <- s ?? ?fmt.Println("write:", s) ?? ?wg.Done() } wg.Add(2) go read() go write() fmt.Println("waiting") wg.Wait()
輸出:
waiting
writing
reading
fatal error: all goroutines are asleep - deadlock!
這種情況并不是報錯空指針,而是死鎖。加上make看看
用var定義channel且make
wg := sync.WaitGroup{} var ch = make(chan string) read := func() { ?? ?fmt.Println("reading") ?? ?s := <-ch ?? ?fmt.Println("read:", s) ?? ?wg.Done() } write := func() { ?? ?fmt.Println("writing") ?? ?s := "t" ?? ?ch <- s ?? ?fmt.Println("write:", s) ?? ?wg.Done() } wg.Add(2) go read() go write()
輸出
waiting
writing
reading
read: t
write: t
這種情況沒什么毛病,之所以先輸出的read,是因為IO機制。下面給寫加上for
直給寫操作加for
wg := sync.WaitGroup{} var ch = make(chan string) read := func() { ?? ?fmt.Println("reading") ?? ?s := <-ch ?? ?fmt.Println("read:", s) ?? ?wg.Done() } write := func() { ?? ?for { ?? ??? ?fmt.Println("writing") ?? ??? ?s := "t" ?? ??? ?ch <- s ?? ??? ?fmt.Println("write:", s) ?? ?} ?? ?wg.Done() } wg.Add(2) go read() go write() fmt.Println("waiting") wg.Wait() fmt.Println("finish")
輸出
waiting
reading
writing
write: t
writing
read: t
fatal error: all goroutines are asleep - deadlock!
報錯說所有的協程都睡著,意思就是runtime發現沒有能拿來調度的協程了,報錯退出。如果是在大項目中,這里則會阻塞,runtime會調度其他可運行的協程。下面把for移到讀操作上。
直給讀操作加for
wg := sync.WaitGroup{}
var ch = make(chan string)
read := func() {
?? ?for {
?? ??? ?fmt.Println("reading")
?? ??? ?s := <-ch
?? ??? ?fmt.Println("read:", s)
?? ?}
?? ?wg.Done()
}
write := func() {
?? ?fmt.Println("writing")
?? ?s := "t"
?? ?ch <- s
?? ?fmt.Println("write:", s)
?? ?wg.Done()
}
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
輸出
waiting
reading
writing
write: t
read: t
reading
fatal error: all goroutines are asleep - deadlock!
跟上面現象基本一樣,不再贅述,然后給倆操作都加上for
讀寫都加for
wg := sync.WaitGroup{} var ch = make(chan string) read := func() { ?? ?for { ?? ??? ?fmt.Println("reading") ?? ??? ?s := <-ch ?? ??? ?fmt.Println("read:", s) ?? ?} ?? ?wg.Done() } write := func() { ?? ?for { ?? ??? ?fmt.Println("writing") ?? ??? ?s := "t" ?? ??? ?ch <- s ?? ??? ?fmt.Println("write:", s) ?? ?} ?? ?wg.Done() } wg.Add(2) go read() go write() fmt.Println("waiting") wg.Wait() fmt.Println("finish")
輸出
waiting
writing
reading
read: t
write: t
writing
reading
read: t
reading
write: t
writing
write: t
writing
...
結果當然就是死循環了,這個很好理解。接下來才是本文的重點:讀數據的第二個參數。我們先保持其他的都不動,在讀的時候接收第二個返回值。
讀channel的第二個返回值
wg := sync.WaitGroup{} var ch = make(chan string) read := func() { ?? ?for { ?? ??? ?fmt.Println("reading") ?? ??? ?s, ok := <-ch ?? ??? ?fmt.Println("read:", s, ok) ?? ?} ?? ?wg.Done() } write := func() { ?? ?for { ?? ??? ?fmt.Println("writing") ?? ??? ?s := "t" ?? ??? ?ch <- s ?? ??? ?fmt.Println("write:", s) ?? ?} ?? ?wg.Done() } wg.Add(2) go read() go write() fmt.Println("waiting") wg.Wait() fmt.Println("finish")
輸出
waiting
writing
reading
read: t true
reading
write: t
writing
write: t
writing
read: t true
reading
read: t true
reading
write: t
...
可以看出來,這第二個返回值是個bool類型,目前全都是true。那么什么時候會是false呢,把channel關上試試。為了更直觀,把字符串的長度一起輸出
關閉channel繼續讀
wg := sync.WaitGroup{} var ch = make(chan string) read := func() { ?? ?for { ?? ??? ?fmt.Println("reading") ?? ??? ?s, ok := <-ch ?? ??? ?fmt.Println("read:", len(s), s, ok) ?? ?} ?? ?wg.Done() } write := func() { ?? ?for i := 0; i < 5; i++ { ?? ??? ?fmt.Println("writing") ?? ??? ?s := "t" ?? ??? ?ch <- s ?? ??? ?fmt.Println("write:", s) ?? ?} ?? ?wg.Done() ?? ?close(ch) } wg.Add(2) go read() go write() fmt.Println("waiting") wg.Wait() fmt.Println("finish")
輸出
waiting
writing
reading
read: 1 t true
reading
write: t
writing
write: t
writing
read: 1 t true
reading
read: 1 t true
reading
write: t
writing
write: t
writing
read: 1 t true
reading
read: 1 t true
reading
write: t
read: 0 ?false
reading
read: 0 ?false
reading
read: 0 ?false
...
接下來就是很規律的死循環了。這樣是不是可以猜測,從已經close的channle讀數據,會讀到該數據類型的零值,且第二個返回值為false?再試試給channel加個buffer,先寫完關上再開始讀
寫完然后關閉channel再開始讀
wg := sync.WaitGroup{} var ch = make(chan string, 5) read := func() { ?? ?for { ?? ??? ?fmt.Println("reading") ?? ??? ?s, ok := <-ch ?? ??? ?fmt.Println("read:", len(s), s, ok) ?? ?} ?? ?wg.Done() } write := func() { ?? ?for i := 0; i < 5; i++ { ?? ??? ?fmt.Println("writing") ?? ??? ?s := "t" ?? ??? ?ch <- s ?? ??? ?fmt.Println("write:", s) ?? ?} ?? ?wg.Done() ?? ?close(ch) ?? ?fmt.Println("closed") } wg.Add(2) write() go read() fmt.Println("waiting") wg.Wait() fmt.Println("finish")
輸出
writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
closed
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 0 ?false
reading
read: 0 ?false
reading
read: 0 ?false
...
我們把寫操作前的go關鍵字去了,并且在關閉channel之后加了log。可以很清晰的看到,先往channel里寫了5次,然后close了,之后才有wait及read的log。并且前5個ok是true,后面循環輸出false?,F在我們可以得出結論當channel關閉且數據都讀完了,再讀數據會讀到該數據類型的零值,且第二個返回值為false。下面再套上select
加個select
wg := sync.WaitGroup{}
var ch = make(chan string, 5)
read := func() {
?? ?for {
?? ??? ?fmt.Println("reading")
?? ??? ?select {
?? ??? ?case s, ok := <-ch:
?? ??? ??? ?fmt.Println("read:", len(s), s, ok)
?? ??? ?}
?? ?}
?? ?wg.Done()
}
write := func() {
?? ?for i := 0; i < 5; i++ {
?? ??? ?fmt.Println("writing")
?? ??? ?s := "t"
?? ??? ?ch <- s
?? ??? ?fmt.Println("write:", s)
?? ?}
?? ?wg.Done()
?? ?close(ch)
?? ?fmt.Println("closed")
}
wg.Add(2)
write()
go read()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
輸出
writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
closed
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 0 ?false
reading
read: 0 ?false
reading
read: 0 ?false
...
很明顯跟上面現象一致,如果忘了關閉channel呢?
channel未及時關閉
wg := sync.WaitGroup{} var ch = make(chan string, 5) read := func() { ?? ?for { ?? ??? ?fmt.Println("reading") ?? ??? ?select { ?? ??? ?case s, ok := <-ch: ?? ??? ??? ?fmt.Println("read:", len(s), s, ok) ?? ??? ?} ?? ?} ?? ?wg.Done() } write := func() { ?? ?for i := 0; i < 5; i++ { ?? ??? ?fmt.Println("writing") ?? ??? ?s := "t" ?? ??? ?ch <- s ?? ??? ?fmt.Println("write:", s) ?? ?} ?? ?wg.Done() ?? ?//close(ch) ?? ?//fmt.Println("closed") } wg.Add(2) write() go read() fmt.Println("waiting") wg.Wait() fmt.Println("finish")
輸出
writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
fatal error: all goroutines are asleep - deadlock!
睡著了,然后報錯。跟上面情況一樣,如果是在大項目中,runtime會調度其他可運行的協程。最后來總結一下怎么操作才算優(sao)雅(qi)。
總結
- 對寫的一方來說,一定記著及時關閉channel,避免出現協程泄露。雖然它占得資源少,省點電不香么。
- 對讀的一方來說,除非十分確定數據的個數,最好是用for來讀數據,省的在“管兒”里有“野數據”造成內存泄露。同時根據第二個返回值的真假來控制for循環,避免出現“無效工作量”
原文鏈接:https://blog.csdn.net/xmh19936688/article/details/106280737
相關推薦
- 2022-02-21 windows11安裝docker desktop
- 2023-07-05 實際開發中如何存儲密碼(md5加鹽bcrypt)golang
- 2022-07-03 canvas文字居中;canvas畫布文字右對齊;canvas畫布文字左對齊;canvas文字自動換
- 2022-05-24 調用無文檔說明的?Web?API過程描述_相關技巧
- 2022-09-17 利用Python提取PDF文本的簡單方法實例_python
- 2023-07-14 react封裝一個router6
- 2022-05-03 C#面向對象設計原則之接口隔離原則_C#教程
- 2021-12-20 Win10配置Hadoop環境變量
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支