網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
寫在前面
這篇文章的誕生要感謝MIT 6.284課程。在其中一節(jié)課中,談到了多線程的協(xié)同的一些問(wèn)題,其中就涉及到了channel這個(gè)概念,并由一段代碼引發(fā)思考并逐漸深入得到了這篇文章。
引子
課程中有一段代碼如下:
其大致含義是:代碼背景是在進(jìn)行多線程網(wǎng)絡(luò)爬蟲頁(yè)面url,master線程啟動(dòng)后,從channel通道中讀取當(dāng)前頁(yè)面的所有url即urls,接著再對(duì)這個(gè)urls中的每一個(gè)url進(jìn)行爬蟲讀取新頁(yè)面中的urls(即執(zhí)行g(shù)o worker(u, ch ,fetcher)),每啟動(dòng)一個(gè)worker線程便開始向channel中寫入該url指向頁(yè)面中所有包含的urls,以供master線程讀取。
問(wèn)題拋出
那么問(wèn)題來(lái)了,為什么第一層for循環(huán)不會(huì)range完ch之后便直接結(jié)束循環(huán),還需要利用局部變量n來(lái)根據(jù)特定情況跳出循環(huán)?
問(wèn)題解釋
課程上的解釋是,這個(gè)range會(huì)一直阻塞,但并未提出解釋。其實(shí),這里很容易分析,因?yàn)楫?dāng)前的channel是一個(gè)無(wú)緩沖通道。所謂無(wú)緩沖通道,簡(jiǎn)單的講就是兩個(gè)線程對(duì)channel進(jìn)行操作,一個(gè)讀,一個(gè)寫,永遠(yuǎn)都只能是寫一個(gè),讀一個(gè)按照這樣的順序進(jìn)行。更詳細(xì)一些的話,讀的那個(gè)線程會(huì)一直阻塞,直到寫的線程向channel中寫入一個(gè)數(shù)據(jù)。反之亦然,寫的線程在完成一次寫操作之后,也會(huì)一直阻塞直到另外一個(gè)線程完成對(duì)該channel的讀取操作。上述情況只有一種例外狀況,那就是該channel通道被某個(gè)線程close掉了:close(channel)。
而這里的range其實(shí)不太等同于對(duì)數(shù)組的range,這里的range實(shí)質(zhì)上為對(duì)channel通道的讀取。所以,在并未有認(rèn)為close通道的前提下,該for循環(huán)會(huì)一直阻塞,不會(huì)退出,于是需要設(shè)定一個(gè)局部狀態(tài)量n讓其退出循環(huán),保證程序的正常運(yùn)行。當(dāng)然我們也可以通過(guò)close其channel來(lái)實(shí)現(xiàn),不過(guò)我認(rèn)為close的時(shí)機(jī)可能不是非常容易把握。
繼續(xù)深入
完成上述思考之后,對(duì)channel進(jìn)行了較為的深入的分析,當(dāng)然分析是以具體的實(shí)驗(yàn)展開的。給出下述實(shí)驗(yàn)代碼:
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") }
執(zhí)行結(jié)果直接報(bào)錯(cuò),顯示:fatal error: all goroutines are asleep - deadlock!
即:出現(xiàn)死鎖。
為什么會(huì)出現(xiàn)這種情況?
首先我們來(lái)分析一下這段代碼的目的:利用channel通道,實(shí)現(xiàn)數(shù)據(jù)的傳遞,一個(gè)線程向channel通道中寫入數(shù)據(jù),另外一個(gè)讀取。為什么會(huì)出現(xiàn)死鎖呢?
首先我們分析一下當(dāng)前程序有多少個(gè)線程在執(zhí)行,main函數(shù)是主線程,調(diào)用test函數(shù)之后,主線程進(jìn)入了test函數(shù)中繼續(xù)運(yùn)行。而在test函數(shù)中,采用閉包函數(shù)或者說(shuō)匿名函數(shù)的方法新開了一個(gè)線程,即goroutine去向已經(jīng)生成的無(wú)緩沖通道中發(fā)送數(shù)據(jù)。發(fā)送的過(guò)程并非是主線程的任務(wù),所以主線程在執(zhí)行完go func之后馬上跳過(guò)繼續(xù)執(zhí)行下面的for循環(huán),也就是要將channel中的數(shù)據(jù)讀取出來(lái)。
for a := range ch { fmt.Print(a) }
這時(shí),問(wèn)題來(lái)了。現(xiàn)在兩個(gè)線程,主線程讀,另外一個(gè)寫。在另外一個(gè)線程完成最后一個(gè)寫之后,主線程開始阻塞等待新的寫操作,而主線程一旦阻塞整個(gè)test函數(shù)也無(wú)法結(jié)束,所以導(dǎo)致了死鎖的產(chǎn)生,主線程一直被阻塞。
明白了上述原因之后,解決方法便很簡(jiǎn)單了,將從channel中讀數(shù)據(jù)的任務(wù)交給另外一個(gè)線程,而非主線程,主線程直接調(diào)用完test函數(shù)之后馬上結(jié)束,其他兩個(gè)線程的死活都不會(huì)影響到程序本身的運(yùn)行,即主線程的運(yùn)行。如下:
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") }
當(dāng)然這種方法是偷懶的,這樣的操作有可能導(dǎo)致內(nèi)存溢出等情況發(fā)生,所以最好還是讓發(fā)送數(shù)據(jù)的線程在發(fā)送完之后將channel關(guān)閉,如下所示:
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,確保主線程在退出之前,負(fù)責(zé)讀取的線程能夠完成讀取工作。
寫在后面
Go語(yǔ)言對(duì)多線程天然的集成性,讓其在處理并發(fā)的一些事務(wù)時(shí)十分方便,但是還是需要注意一些死鎖的生成。
原文鏈接:https://blog.csdn.net/weixin_38107316/article/details/113485780
相關(guān)推薦
- 2022-10-19 python列表[list]和元組(tuple)詳情_python
- 2023-06-21 python?__add__()的具體使用_python
- 2022-12-27 python如何查找圖片按鈕的坐標(biāo)位置_python
- 2022-03-30 Docker搭建RabbitMQ集群的方法步驟_docker
- 2022-03-14 xampp配置ssl 端口443后,無(wú)法開啟服務(wù)。提示端口被占用
- 2023-07-05 Spring aop+自定義注解實(shí)現(xiàn)接口返回?cái)?shù)據(jù)過(guò)濾
- 2022-05-06 嵌入式C語(yǔ)言輕量級(jí)程序架構(gòu)內(nèi)核編寫_C 語(yǔ)言
- 2022-05-24 淺談C#中Action和Func回調(diào)的常用方式_C#教程
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支