網站首頁 編程語言 正文
寫在前面
這篇文章的誕生要感謝MIT 6.284課程。在其中一節課中,談到了多線程的協同的一些問題,其中就涉及到了channel這個概念,并由一段代碼引發思考并逐漸深入得到了這篇文章。
引子
課程中有一段代碼如下:
其大致含義是:代碼背景是在進行多線程網絡爬蟲頁面url,master線程啟動后,從channel通道中讀取當前頁面的所有url即urls,接著再對這個urls中的每一個url進行爬蟲讀取新頁面中的urls(即執行go worker(u, ch ,fetcher)),每啟動一個worker線程便開始向channel中寫入該url指向頁面中所有包含的urls,以供master線程讀取。
問題拋出
那么問題來了,為什么第一層for循環不會range完ch之后便直接結束循環,還需要利用局部變量n來根據特定情況跳出循環?
問題解釋
課程上的解釋是,這個range會一直阻塞,但并未提出解釋。其實,這里很容易分析,因為當前的channel是一個無緩沖通道。所謂無緩沖通道,簡單的講就是兩個線程對channel進行操作,一個讀,一個寫,永遠都只能是寫一個,讀一個按照這樣的順序進行。更詳細一些的話,讀的那個線程會一直阻塞,直到寫的線程向channel中寫入一個數據。反之亦然,寫的線程在完成一次寫操作之后,也會一直阻塞直到另外一個線程完成對該channel的讀取操作。上述情況只有一種例外狀況,那就是該channel通道被某個線程close掉了:close(channel)。
而這里的range其實不太等同于對數組的range,這里的range實質上為對channel通道的讀取。所以,在并未有認為close通道的前提下,該for循環會一直阻塞,不會退出,于是需要設定一個局部狀態量n讓其退出循環,保證程序的正常運行。當然我們也可以通過close其channel來實現,不過我認為close的時機可能不是非常容易把握。
繼續深入
完成上述思考之后,對channel進行了較為的深入的分析,當然分析是以具體的實驗展開的。給出下述實驗代碼:
func main() { test() } func test() { ch := make(chan int,4) go func() { ch <- 1 ch <- 2 ch <- 3 ch <- 4 }() //go func() { for a := range ch { fmt.Print(a) } //}() fmt.Print("test is over") }
執行結果直接報錯,顯示:fatal error: all goroutines are asleep - deadlock!
即:出現死鎖。
為什么會出現這種情況?
首先我們來分析一下這段代碼的目的:利用channel通道,實現數據的傳遞,一個線程向channel通道中寫入數據,另外一個讀取。為什么會出現死鎖呢?
首先我們分析一下當前程序有多少個線程在執行,main函數是主線程,調用test函數之后,主線程進入了test函數中繼續運行。而在test函數中,采用閉包函數或者說匿名函數的方法新開了一個線程,即goroutine去向已經生成的無緩沖通道中發送數據。發送的過程并非是主線程的任務,所以主線程在執行完go func之后馬上跳過繼續執行下面的for循環,也就是要將channel中的數據讀取出來。
for a := range ch { fmt.Print(a) }
這時,問題來了?,F在兩個線程,主線程讀,另外一個寫。在另外一個線程完成最后一個寫之后,主線程開始阻塞等待新的寫操作,而主線程一旦阻塞整個test函數也無法結束,所以導致了死鎖的產生,主線程一直被阻塞。
明白了上述原因之后,解決方法便很簡單了,將從channel中讀數據的任務交給另外一個線程,而非主線程,主線程直接調用完test函數之后馬上結束,其他兩個線程的死活都不會影響到程序本身的運行,即主線程的運行。如下:
func main() { test() } func test() { ch := make(chan int,4) go func() { ch <- 1 ch <- 2 ch <- 3 ch <- 4 }() go func() { for a := range ch { fmt.Print(a) } }() fmt.Print("test is over") }
當然這種方法是偷懶的,這樣的操作有可能導致內存溢出等情況發生,所以最好還是讓發送數據的線程在發送完之后將channel關閉,如下所示:
func main() { test() time.Sleep(time.Second) } func test() { ch := make(chan int,4) go func() { ch <- 1 ch <- 2 ch <- 3 ch <- 4 close(ch) }() go func() { for a := range ch { fmt.Print(a) } }() fmt.Print("test is over") }
輸出為:
test is over1234
注意,這里為了保證能夠輸出1234,需要將主線程休眠1s,確保主線程在退出之前,負責讀取的線程能夠完成讀取工作。
寫在后面
Go語言對多線程天然的集成性,讓其在處理并發的一些事務時十分方便,但是還是需要注意一些死鎖的生成。
原文鏈接:https://blog.csdn.net/weixin_38107316/article/details/113485780
相關推薦
- 2022-12-10 Flutter改變狀態變量是否必須寫在setState回調詳解_Android
- 2022-01-21 Flink中實現自定義ProcessFunction實現定時器、側輸出
- 2022-11-09 css實現div盒子旋轉
- 2022-08-08 基于Redis緩存數據常見的三種問題及解決_Redis
- 2022-06-06 Python數據可視化Pyecharts制作Heatmap熱力圖_python
- 2022-08-12 Python學習之字典的創建和使用_python
- 2022-11-20 Postgresql刪除數據庫表中重復數據的幾種方法詳解_PostgreSQL
- 2022-10-26 Google?Kubernetes?Engine?集群實戰詳解_云其它
- 最近更新
-
- 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同步修改后的遠程分支