中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

別人做的網(wǎng)站上海做關(guān)鍵詞推廣企業(yè)

別人做的網(wǎng)站,上海做關(guān)鍵詞推廣企業(yè),化妝品的網(wǎng)站建設(shè),鐵嶺網(wǎng)站開(kāi)發(fā)文章目錄 協(xié)程(goroutine)基本介紹GMP模型協(xié)程間共享變量 通道(channel)基本介紹channel的定義方式channel的讀寫channel的關(guān)閉channel的遍歷方式只讀/只寫channelchannel最佳案例select語(yǔ)句 協(xié)程(goroutine&#xff0…

文章目錄

  • 協(xié)程(goroutine)
    • 基本介紹
    • GMP模型
    • 協(xié)程間共享變量
  • 通道(channel)
    • 基本介紹
    • channel的定義方式
    • channel的讀寫
    • channel的關(guān)閉
    • channel的遍歷方式
    • 只讀/只寫channel
    • channel最佳案例
    • select語(yǔ)句

協(xié)程(goroutine)

基本介紹

基本介紹

進(jìn)程、線程與協(xié)程:

  • 進(jìn)程(Process)是計(jì)算機(jī)中正在運(yùn)行的程序的實(shí)例,是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位。每個(gè)進(jìn)程都有自己獨(dú)立的地址空間、代碼、數(shù)據(jù)和文件資源。進(jìn)程之間相互獨(dú)立,通過(guò)進(jìn)程間通信機(jī)制進(jìn)行數(shù)據(jù)交換和協(xié)作。進(jìn)程的創(chuàng)建、銷毀以及切換都由操作系統(tǒng)自動(dòng)完成,開(kāi)銷較大。
  • 線程(Thread)是操作系統(tǒng)調(diào)度的最小執(zhí)行單元,是進(jìn)程內(nèi)的一個(gè)執(zhí)行路徑。線程與進(jìn)程共享同一地址空間和大部分資源,包括代碼段、數(shù)據(jù)段和打開(kāi)的文件等。線程之間通常借助互斥鎖、條件變量以及信號(hào)量等進(jìn)行數(shù)據(jù)交換。線程的創(chuàng)建、銷毀以及切換的開(kāi)銷較小,但需要注意線程間的同步和共享資源的管理。
  • 協(xié)程(Coroutine)協(xié)程是一種輕量級(jí)的并發(fā)執(zhí)行單元,通常由編程語(yǔ)言本身的運(yùn)行時(shí)系統(tǒng)進(jìn)行調(diào)度和管理。協(xié)程通常在一個(gè)線程內(nèi)執(zhí)行,共享相同的地址空間和資源。協(xié)程間通常通過(guò)通道(Channel)實(shí)現(xiàn)數(shù)據(jù)交換和協(xié)作。協(xié)程的創(chuàng)建、銷毀以及切換都由運(yùn)行時(shí)系統(tǒng)自動(dòng)完成,開(kāi)銷非常小,可以創(chuàng)建成千上萬(wàn)個(gè)協(xié)程而不會(huì)導(dǎo)致系統(tǒng)負(fù)載過(guò)高。

并發(fā)與并行:

  • 并發(fā)(Concurrency)指的是在單個(gè)處理器上以時(shí)間片輪轉(zhuǎn)的方式交替執(zhí)行多個(gè)任務(wù),使得在一段時(shí)間內(nèi),這多個(gè)任務(wù)都得以推進(jìn),但實(shí)際在一個(gè)時(shí)間點(diǎn)只有一個(gè)任務(wù)在執(zhí)行。
  • 并行(Parallelism)指的是多個(gè)任務(wù)同時(shí)在不同的處理器上執(zhí)行,使得這多個(gè)任務(wù)同時(shí)得以推進(jìn),并且在一個(gè)時(shí)間點(diǎn)來(lái)看,也是多個(gè)任務(wù)在同時(shí)執(zhí)行。

在Go中,通過(guò)在函數(shù)或方法的調(diào)用前加上go關(guān)鍵字即可創(chuàng)建一個(gè)go協(xié)程,并讓其運(yùn)行對(duì)應(yīng)的函數(shù)或方法。如下:

package mainimport ("fmt""time"
)func Print() bool {for i := 0; i < 10; i++ {fmt.Printf("Print: hello goroutine...%d\n", i+1)time.Sleep(time.Second)}return true
}func main() {go Print() // 創(chuàng)建go協(xié)程for i := 0; i < 5; i++ {fmt.Printf("main: hello goroutine...%d\n", i+1)time.Sleep(time.Second)}
}

在上述代碼中,主協(xié)程創(chuàng)建了一個(gè)新協(xié)程用于執(zhí)行Print函數(shù),主協(xié)程進(jìn)行5次打印后退出,新協(xié)程進(jìn)行10次打印后退出。運(yùn)行結(jié)果如下:

在這里插入圖片描述

說(shuō)明一下:

  • 在Go中,當(dāng)程序啟動(dòng)時(shí)會(huì)自動(dòng)創(chuàng)建一個(gè)主協(xié)程來(lái)執(zhí)行main函數(shù),該協(xié)程與其他新創(chuàng)建的協(xié)程沒(méi)有本質(zhì)的區(qū)別,但主協(xié)程執(zhí)行完畢后整個(gè)程序會(huì)退出,即使其他協(xié)程還未執(zhí)行完畢,也會(huì)跟著退出。
  • 如果一個(gè)協(xié)程在執(zhí)行過(guò)程中觸發(fā)了panic異常,但沒(méi)有對(duì)其進(jìn)行捕獲,那么會(huì)導(dǎo)致整個(gè)程序崩潰,因此在協(xié)程中也需要通過(guò)recover函數(shù)對(duì)panic進(jìn)行捕獲。

GMP模型

常規(guī)的協(xié)程(Coroutine)

線程是在內(nèi)核態(tài)視角下的最小執(zhí)行單元,而協(xié)程是在線程的基礎(chǔ)上,在用戶態(tài)視角下進(jìn)行二次開(kāi)發(fā)得到的更小的執(zhí)行單元。常規(guī)的協(xié)程(Coroutine)通常是與一個(gè)線程強(qiáng)綁定的,而一個(gè)線程可以綁定多個(gè)協(xié)程。如下:

在這里插入圖片描述

說(shuō)明一下:

  • 由于常規(guī)的協(xié)程是與一個(gè)線程強(qiáng)綁定的,因此綁定于同一線程的多個(gè)協(xié)程只能做到并發(fā),無(wú)法做到并行。
  • 當(dāng)一個(gè)協(xié)程因?yàn)槟承┰蛳萑胱枞?#xff0c;那么這個(gè)阻塞會(huì)直接上升到對(duì)應(yīng)的線程,最終導(dǎo)致整個(gè)協(xié)程組陷入阻塞。

Go中的協(xié)程(Goroutine)

Go語(yǔ)言中的協(xié)程(Goroutine)與常規(guī)的協(xié)程(Coroutine)的實(shí)現(xiàn)方式有所不同,Go中的協(xié)程不是與一個(gè)線程強(qiáng)綁定的,而是由Go調(diào)度器動(dòng)態(tài)的將協(xié)程綁定到可用的線程上執(zhí)行。如下:

在這里插入圖片描述

說(shuō)明一下:

  • 由于Go協(xié)程與線程之間的綁定是動(dòng)態(tài)的,因此各個(gè)協(xié)程之間既能做到并發(fā),也能做到并行。
  • 當(dāng)一個(gè)Go協(xié)程因?yàn)槟承┰蛳萑胱枞?#xff0c;那么Go調(diào)度器會(huì)將該協(xié)程與其綁定的線程進(jìn)行解綁,將線程的資源釋放出來(lái),使得線程可以與其他可調(diào)度的協(xié)程進(jìn)行綁定。

GMP模型

GMP(Goroutine-Machine-Processor)模型是Go運(yùn)行時(shí)系統(tǒng)中用于實(shí)現(xiàn)并發(fā)執(zhí)行的模型,負(fù)責(zé)管理和調(diào)度協(xié)程的執(zhí)行。G、M和P的含義分別如下:

  • G(Goroutine):代表Go中的協(xié)程,每個(gè)G都有自己的運(yùn)行棧、狀態(tài)以及執(zhí)行的任務(wù)函數(shù)。
  • M(Machine):代表Go中的線程,M不直接執(zhí)行G,而是先和P綁定,由P來(lái)指定M所需執(zhí)行的G。
  • P(Processor):代表Go中的調(diào)度器,P實(shí)現(xiàn)G和M之間的動(dòng)態(tài)有機(jī)結(jié)合。對(duì)于G而言,P就是其CPU,G只有被P調(diào)度才得以執(zhí)行;對(duì)于M而言,P是其執(zhí)行代理,為其指定可執(zhí)行的G。

GMP模型示意圖如下:

在這里插入圖片描述

上圖說(shuō)明:

  • 全局有多個(gè)M和多個(gè)P,但M和P的數(shù)量不一定是相同的。每個(gè)M在調(diào)度G之前,需要先和P進(jìn)行綁定(不是強(qiáng)綁定),每個(gè)M調(diào)度的G由其對(duì)應(yīng)的P指定。M無(wú)需記錄所調(diào)度的G的狀態(tài)信息,因此G在全生命周期中可以實(shí)現(xiàn)跨M執(zhí)行。
  • 在GMP模型中有三種隊(duì)列來(lái)存放G,分別是全局隊(duì)列、P的本地隊(duì)列和wait隊(duì)列(用于存放io阻塞就緒態(tài)的G,圖中未展示)。
  • 每個(gè)P都有一個(gè)對(duì)應(yīng)本地隊(duì)列,訪問(wèn)本地隊(duì)列時(shí)可以接近無(wú)鎖化。當(dāng)P為M獲取可調(diào)度的G時(shí),會(huì)優(yōu)先從自己的本地隊(duì)列中進(jìn)行獲取,其次從全局隊(duì)列中獲取,最后從wait隊(duì)列中獲取。
  • 如果一個(gè)G在調(diào)度過(guò)程中新創(chuàng)建了一個(gè)G,那么這個(gè)新G會(huì)優(yōu)先投遞到當(dāng)前P的本地隊(duì)列中,如果本地隊(duì)列已滿則投遞到全局隊(duì)列中。

調(diào)度器P獲取可調(diào)度的G的流程如下:

  1. 優(yōu)先嘗試從當(dāng)前P的本地隊(duì)列獲取可調(diào)度的G。
  2. 嘗試從全局隊(duì)列獲取可調(diào)度的G。
  3. 嘗試從wait隊(duì)列獲取io阻塞就緒的G。
  4. 嘗試從其他P的本地隊(duì)列竊取一半的G補(bǔ)充到當(dāng)前P的本地隊(duì)列,防止不同P的閑忙差異過(guò)大(work-stealing機(jī)制)。

說(shuō)明一下:

  • 由于存在work-stealing機(jī)制,因此P的本地隊(duì)列的訪問(wèn)也不是完全無(wú)鎖的,只能說(shuō)接近無(wú)鎖化。
  • 上述說(shuō)到的只是獲取可調(diào)度的G的主要流程,實(shí)際實(shí)現(xiàn)時(shí)還有更多的細(xì)節(jié)。比如P每進(jìn)行61次調(diào)度后,會(huì)先嘗試從全局隊(duì)列中獲取一個(gè)G進(jìn)行調(diào)度,避免造成全局隊(duì)列中的G的饑餓問(wèn)題。

GOMAXPROCS

在GMP模型中,G只有被P調(diào)度才得以執(zhí)行,因此P的數(shù)量決定了G的最大并行數(shù)量。通過(guò)runtime包中的GOMAXPROCS函數(shù)可以獲取和設(shè)置P的數(shù)量。如下:

package mainimport ("fmt""runtime"
)func main() {cpuNum := runtime.NumCPU()          // 獲取本地機(jī)器的邏輯CPU數(shù)fmt.Printf("cpuNum = %d\n", cpuNum) // cpuNum = 6runtime.GOMAXPROCS(4)         // 設(shè)置可同時(shí)執(zhí)行的最大CPU數(shù)num := runtime.GOMAXPROCS(0)  // 獲取可同時(shí)執(zhí)行的最大CPU數(shù)fmt.Printf("num = %d\n", num) // num = 4
}

說(shuō)明一下:

  • runtime包中的NumCPU函數(shù),用于獲取本地機(jī)器的邏輯CPU數(shù)。
  • runtime包中的GOMAXPROCS函數(shù),用于設(shè)置可同時(shí)執(zhí)行的最大CPU數(shù),并返回先前的設(shè)置。如果設(shè)置的值小于1,則不會(huì)更改當(dāng)前的值,設(shè)置的值超過(guò)CPU核數(shù)無(wú)意義。
  • 從Go1.5開(kāi)始,GOMAXPROCS默認(rèn)設(shè)置為CPU的核數(shù),并且可以根據(jù)需要自動(dòng)調(diào)整并發(fā)執(zhí)行的并行度,無(wú)需再手動(dòng)設(shè)置。

協(xié)程的生命周期

Go中協(xié)程的生命周期大致由如下幾種狀態(tài)組成:

  • _Gidle:表示該協(xié)程剛剛創(chuàng)建,但還未進(jìn)行初始化。
  • _Gdead:表示該協(xié)程已經(jīng)完成初始化,但還未被使用。
  • _Grunnable:表示該協(xié)程已經(jīng)被放入運(yùn)行隊(duì)列,但還未被調(diào)度。
  • _Grunning:表示該協(xié)程正在被調(diào)度。
  • _Gsyscall:表示該協(xié)程正在執(zhí)行系統(tǒng)調(diào)用。
  • _Gwaiting:表示該協(xié)程處于掛起狀態(tài),需要等待被喚醒。
  • _Gdead:表示該協(xié)程剛剛執(zhí)行完畢。

狀態(tài)轉(zhuǎn)換如下:

在這里插入圖片描述

說(shuō)明一下:

  • 當(dāng)協(xié)程在調(diào)度過(guò)程中執(zhí)行到系統(tǒng)調(diào)用代碼時(shí),其狀態(tài)就會(huì)由_Grunning切換為_(kāi)Gsyscall,并在系統(tǒng)調(diào)用結(jié)束后根據(jù)實(shí)際情況恢復(fù)為_(kāi)Grunning或_Grunnable狀態(tài)。
  • 協(xié)程在調(diào)度過(guò)程中,可能因?yàn)槟承┰蚨萑胱枞?#xff0c;比如等待鎖資源就緒或等待channel條件就緒等,這是協(xié)程的狀態(tài)會(huì)由_Grunning切換為_(kāi)Gwaiting,并在協(xié)程被喚醒后恢復(fù)為_(kāi)Grunnable狀態(tài)。
  • 除了上述常見(jiàn)的協(xié)程狀態(tài)外,協(xié)程還有一些其他的狀態(tài),比如_Gcopystack表示該協(xié)程正處于棧擴(kuò)容流程中(Go協(xié)程的??臻g大小可動(dòng)態(tài)擴(kuò)縮),_Greempted表示協(xié)程被搶占后的狀態(tài)。

協(xié)程的調(diào)度流程

GMP模型中存在三種類型的協(xié)程:

  • 普通的g:用戶通過(guò)go關(guān)鍵字創(chuàng)建的協(xié)程,也就是GMP模型中需要被調(diào)度的G。
  • g0:特殊的調(diào)度協(xié)程,每個(gè)M都有一個(gè)g0,其主要負(fù)責(zé)對(duì)普通的g進(jìn)行運(yùn)行調(diào)度。
  • monitor g:全局監(jiān)控協(xié)程,monitor g會(huì)越過(guò)P直接與一個(gè)M進(jìn)行綁定,不斷輪詢對(duì)所有P的執(zhí)行狀況進(jìn)行監(jiān)控,如果發(fā)現(xiàn)滿足搶占調(diào)度的條件,則會(huì)從第三方的角度出手干預(yù),主動(dòng)發(fā)起搶占調(diào)度。

在創(chuàng)建M時(shí),Go運(yùn)行時(shí)系統(tǒng)會(huì)為每個(gè)M初始化一個(gè)g0,g0的調(diào)度流程如下:

  1. 找到一個(gè)可被調(diào)度執(zhí)行的G。
  2. 將這個(gè)G的狀態(tài)切換為_(kāi)Grunning,并通過(guò)調(diào)用gogo函數(shù)將執(zhí)行權(quán)交給G。
  3. 執(zhí)行G的代碼邏輯,直到某些條件達(dá)成使得調(diào)度結(jié)束。
  4. G調(diào)度結(jié)束后,通過(guò)調(diào)用mcall函數(shù)將執(zhí)行權(quán)交還給g0,并更新G的狀態(tài)。

示意圖如下:

在這里插入圖片描述

調(diào)度類型

GMP模型中的調(diào)度類型大致可分為如下四類:

  • 主動(dòng)調(diào)度:用戶通過(guò)調(diào)用runtime包中的Gosched函數(shù),可以讓當(dāng)前G主動(dòng)讓出執(zhí)行權(quán),并將其投遞到全局隊(duì)列中等待下一次調(diào)度。
  • 被動(dòng)調(diào)度:G在調(diào)度過(guò)程中,因?yàn)槟承┰蚨萑胱枞鴮?dǎo)致調(diào)度終止,比如等待鎖資源就緒或等待channel條件就緒等。
  • 正常調(diào)度:G的代碼邏輯被正常執(zhí)行完畢,調(diào)度終止。
  • 搶占調(diào)度:在G執(zhí)行系統(tǒng)調(diào)用的情況下,如果滿足了搶占調(diào)度的條件,那么monitor g會(huì)強(qiáng)行將當(dāng)前的P和M進(jìn)行解綁,讓解綁后的P重新尋找一個(gè)空閑的M進(jìn)行綁定,進(jìn)而可以繼續(xù)調(diào)度其他的G,而解綁后M則繼續(xù)執(zhí)行系統(tǒng)調(diào)用。

觸發(fā)前三種調(diào)度類型中的任意一種,都會(huì)導(dǎo)致當(dāng)前G的調(diào)度終止,此時(shí)M的執(zhí)行權(quán)將由普通的g交還給g0。示意圖如下:

在這里插入圖片描述

上圖說(shuō)明:

  • g0在調(diào)度普通的g時(shí),會(huì)先通過(guò)findRunnable函數(shù)找到一個(gè)可被調(diào)度的G,然后通過(guò)execute函數(shù)更新對(duì)應(yīng)G和P的狀態(tài)信息,最后通過(guò)gogo函數(shù)將執(zhí)行權(quán)交給G,進(jìn)行G的調(diào)度。
  • G在調(diào)度過(guò)程中,如果因?yàn)橹鲃?dòng)調(diào)度、被動(dòng)調(diào)度或正常調(diào)度導(dǎo)致調(diào)度終止,那么會(huì)先調(diào)用mcall函數(shù)將執(zhí)行權(quán)交還給g0,然后通過(guò)調(diào)用對(duì)應(yīng)的函數(shù)更新G的狀態(tài)信息,并完成G和M解綁等操作,然后開(kāi)啟新一輪的調(diào)度。
  • gosched_m函數(shù)對(duì)應(yīng)的是主動(dòng)調(diào)度,該函數(shù)會(huì)先將G的狀態(tài)由_Grunning切換為_(kāi)Grunnable,然后將G和M解綁并將其投遞到全局隊(duì)列中,最后開(kāi)啟新一輪的調(diào)度。
  • park_m函數(shù)對(duì)應(yīng)的是被動(dòng)調(diào)度,該函數(shù)會(huì)先將G的狀態(tài)由_Grunning切換為_(kāi)Gwaiting,然后將G和M解綁,最后開(kāi)啟新一輪的調(diào)度。
  • goexit0函數(shù)對(duì)應(yīng)的是正常調(diào)度,該函數(shù)會(huì)先將G的狀態(tài)由_Grunning切換為_(kāi)Gdead,然后將G和M解綁,最后開(kāi)啟新一輪的調(diào)度。

關(guān)于被動(dòng)調(diào)度:

  • 當(dāng)因被動(dòng)調(diào)度陷入阻塞的G對(duì)應(yīng)的條件就緒時(shí),會(huì)由導(dǎo)致條件就緒的G執(zhí)行g(shù)oready函數(shù)將其喚醒,喚醒時(shí)會(huì)先將G的狀態(tài)由_Gwaiting切換為_(kāi)Grunnable,然后將其添加到喚醒者的P的本地隊(duì)列中。
  • 比如某個(gè)G在申請(qǐng)鎖時(shí)由于鎖資源不就緒而陷入阻塞,此時(shí)這個(gè)G會(huì)被放在鎖對(duì)應(yīng)的資源等待隊(duì)列中,當(dāng)另一個(gè)持有鎖的G在被調(diào)度的過(guò)程中執(zhí)行釋放鎖操作時(shí),就會(huì)執(zhí)行g(shù)oready函數(shù)喚醒該鎖對(duì)應(yīng)的資源等待隊(duì)列中的G,并將其添加到自己的P的本地隊(duì)列中。
  • 在調(diào)度喚醒者時(shí)M的執(zhí)行權(quán)在普通的g手中,而被喚醒者的狀態(tài)切換操作以及G的投遞操作需要由g0執(zhí)行,因此在goready函數(shù)中會(huì)先將執(zhí)行權(quán)交還給g0,并在執(zhí)行喚醒操作后再重新獲得執(zhí)行權(quán),這里的執(zhí)行權(quán)交接是通過(guò)systemstack函數(shù)完成的。
  • goready函數(shù)在將喚醒的G添加到喚醒者的P的本地隊(duì)列中時(shí),如果P的本地隊(duì)列已滿,則會(huì)將喚醒的G以及P的本地隊(duì)列中一半的G放回到全局隊(duì)列中,幫助當(dāng)前的P緩解執(zhí)行壓力。

關(guān)于搶占調(diào)度:

  • 在G需要執(zhí)行系統(tǒng)調(diào)用之前,會(huì)先調(diào)用reentersyscall函數(shù)保存當(dāng)前G的執(zhí)行環(huán)境,并將G和P的狀態(tài)更新為對(duì)應(yīng)的系統(tǒng)調(diào)用狀態(tài),最后解除P和當(dāng)前M之間的綁定,因?yàn)镸即將進(jìn)入系統(tǒng)調(diào)用而導(dǎo)致短暫不可用。與M解除綁定關(guān)系的P會(huì)被添加到當(dāng)前M的oldp容器中,后續(xù)M執(zhí)行完系統(tǒng)調(diào)用后會(huì)優(yōu)先尋找該P(yáng)重新建立綁定關(guān)系。
  • 在G執(zhí)行系統(tǒng)調(diào)用期間,如果P的本地隊(duì)列不為空,或者當(dāng)前沒(méi)有空閑的M和P,或者G執(zhí)行系統(tǒng)調(diào)用的時(shí)間超過(guò)10ms,則monitor g會(huì)將當(dāng)前M的oldp容器中的P的狀態(tài)置為空閑,并讓其與其他空閑的M(也可能新創(chuàng)建一個(gè)M)進(jìn)行綁定,進(jìn)而可以繼續(xù)調(diào)度其他的G,而當(dāng)前的M仍然繼續(xù)執(zhí)行系統(tǒng)調(diào)用。
  • 當(dāng)M執(zhí)行完系統(tǒng)調(diào)用后,會(huì)通過(guò)exitsyscall函數(shù)嘗試尋找P進(jìn)行綁定。如果此時(shí)M的oldp容器中的P仍然可用,則重新與該P(yáng)建立綁定關(guān)系,并將G的狀態(tài)重新置為_(kāi)Grunning,繼續(xù)執(zhí)行后續(xù)的代碼邏輯。如果原先的P已經(jīng)不可用,則將G的狀態(tài)置為_(kāi)Grunnable,并解除G和M的綁定關(guān)系,嘗試從全局P隊(duì)列中尋找一個(gè)可用的P進(jìn)行綁定,如果找到了則在綁定對(duì)應(yīng)的P后繼續(xù)調(diào)度該G,否則將該G投遞到全局隊(duì)列,并讓當(dāng)前的M陷入沉睡,直到被喚醒后再繼續(xù)發(fā)起調(diào)度。

協(xié)程間共享變量

協(xié)程間共享變量

  • 在協(xié)程之間共享變量是常見(jiàn)的需求,以便協(xié)程之間能夠進(jìn)行數(shù)據(jù)交換和協(xié)同工作。
  • 為了保證共享資源的并發(fā)安全,通常需要引入互斥鎖對(duì)共享資源進(jìn)行保護(hù)。

例如,下面程序中啟動(dòng)了4個(gè)協(xié)程進(jìn)行搶票,在搶票過(guò)程中需要并發(fā)訪問(wèn)全局變量tickets,代碼中通過(guò)加鎖的方式保證了tickets變量的并發(fā)安全。如下:

package mainimport ("fmt""sync""time"
)var (tickets = 1000     // 共享資源mtx     sync.Mutex // 互斥鎖
)func ByTicket(id int) {for {mtx.Lock() // 加鎖if tickets <= 0 {mtx.Unlock() // 解鎖break}time.Sleep(time.Microsecond) // 模擬搶票過(guò)程的耗時(shí)tickets--fmt.Printf("goroutine %d get a ticket, tickets = %d\n", id, tickets)mtx.Unlock() // 解鎖}
}func main() {// 啟動(dòng)4個(gè)協(xié)程進(jìn)行搶票for i := 0; i < 4; i++ {go ByTicket(i)}for {if tickets <= 0 {break}}fmt.Printf("tickets sold out...tickets = %d\n", tickets)
}

說(shuō)明一下:

  • Mutex是sync包中的互斥鎖類型,用于保護(hù)共享資源的并發(fā)訪問(wèn),該類型提供了Lock和Unlock兩個(gè)方法,分別用于加鎖和解鎖。

通道(channel)

基本介紹

基本介紹

  • 通道(channel)是Go中用于協(xié)程間通信和數(shù)據(jù)交換的機(jī)制,其提供了一種安全、同步和高效的方式來(lái)傳遞數(shù)據(jù),以實(shí)現(xiàn)協(xié)程之間的通信和協(xié)同工作。
  • channel本質(zhì)是一個(gè)隊(duì)列,遵守先進(jìn)先出(FIFO)的原則。channel本身是線程安全的,多協(xié)程可以通過(guò)channel直接發(fā)送和接收數(shù)據(jù),顯式的加鎖解鎖操作。

channel的示意圖如下:

在這里插入圖片描述

channel的定義方式

channel的定義方式

在定義channel時(shí),通過(guò)make創(chuàng)建指定類型以及容量的channel。如下:

package mainimport ("fmt""unsafe"
)func main() {// make channelvar intChan = make(chan int, 10)fmt.Printf("intChan type = %T\n", intChan)                // intChan type = chan intfmt.Printf("intChan len = %d\n", len(intChan))            // intChan len = 0fmt.Printf("intChan cap = %d\n", cap(intChan))            // intChan cap = 10fmt.Printf("intChan size = %d\n", unsafe.Sizeof(intChan)) // intChan size = 8
}

說(shuō)明一下:

  • channel是引用類型,其定義后需要先通過(guò)make函數(shù)分配內(nèi)存空間,然后才能使用。在使用make函數(shù)為channel分配內(nèi)存空間時(shí),其第一個(gè)參數(shù)表示channel的類型,第二個(gè)參數(shù)表示channel的容量,第二個(gè)參數(shù)若省略則默認(rèn)為0。
  • 通過(guò)len函數(shù)可以獲取channel中元素的數(shù)量,通過(guò)cap函數(shù)可以獲取channel的容量。channel中僅包含一個(gè)指向底層隊(duì)列的指針,屬于引用類型,因此channel類型變量的大小為8字節(jié)。
  • channel中只能存放對(duì)應(yīng)類型的數(shù)據(jù),如果想讓channel存放任意類型的數(shù)據(jù),可以指定channel中存放的元素類型為interface{}。

channel的讀寫

channel的讀寫

channel的讀寫:

  • 通過(guò)channel <- data的方式向channel中寫入數(shù)據(jù),在寫入數(shù)據(jù)時(shí),如果channel已滿,則寫操作會(huì)被阻塞,直到channel中有數(shù)據(jù)被讀走,再執(zhí)行寫操作。
  • 通過(guò)data := <-channel的方式從channel中讀取數(shù)據(jù),在讀取數(shù)據(jù)時(shí),如果channel為空,則讀操作會(huì)被阻塞,直到有數(shù)據(jù)寫入channel中,再執(zhí)行讀操作。

例如,下面程序中定義了一個(gè)容量為5的channel,并啟動(dòng)了一個(gè)協(xié)程不斷向該channel中寫入數(shù)據(jù),而在主協(xié)程中每隔1秒從該channel中讀取一次數(shù)據(jù)。如下:

package mainimport ("fmt""time"
)func WriteNum(intChan chan int) {num := 0for {intChan <- num // 向channel中寫入數(shù)據(jù)fmt.Printf("write a num: %d\n", num)num++}
}func ReadNum(intChan chan int) {for {time.Sleep(time.Second)num := <-intChan // 從channel中讀取數(shù)據(jù)fmt.Printf("read a num: %d\n", num)}
}func main() {intChan := make(chan int, 5)go WriteNum(intChan)ReadNum(intChan)
}

在上述代碼中,由于向channel中寫入數(shù)據(jù)的過(guò)程中沒(méi)有進(jìn)行任何休眠操作,因此程序運(yùn)行后channel立馬被寫滿了,此時(shí)對(duì)channel的寫操作將會(huì)被阻塞,直到channel中的數(shù)據(jù)被主協(xié)程讀走,才能再次執(zhí)行寫操作,因此后續(xù)對(duì)channel的寫操作也被同步為每秒一次。程序運(yùn)行結(jié)果如下:

在這里插入圖片描述

說(shuō)明一下:

  • 將channel的容量指定為0,意味著channel中不能存儲(chǔ)任何數(shù)據(jù),此時(shí)該channel將成為一個(gè)無(wú)緩沖通道。對(duì)無(wú)緩沖通道的寫操作將會(huì)被阻塞,直到有另一個(gè)協(xié)程準(zhǔn)備對(duì)channel進(jìn)行讀操作,反之亦然,因此無(wú)緩沖通道是一種強(qiáng)制同步的機(jī)制。

channel的關(guān)閉

channel的關(guān)閉

在Go中,通過(guò)內(nèi)建函數(shù)close可以關(guān)閉指定的channel,channel關(guān)閉后不能再對(duì)其進(jìn)行寫操作,否則會(huì)觸發(fā)panic異常,但仍可以從該channel中讀取數(shù)據(jù)。如下:

package mainimport "fmt"func main() {charChan := make(chan int, 10)for i := 0; i < 10; i++ {charChan <- 'a' + i}close(charChan) // 關(guān)閉channelfor {ch, ok := <-charChanif !ok {break}fmt.Printf("read a char: %c\n", ch)}
}

運(yùn)行程序后可以看到,channel雖然被關(guān)閉了,但仍然可以讀取channel中的數(shù)據(jù)。如下:

在這里插入圖片描述

說(shuō)明一下:

  • 通過(guò)<-channel的方式讀取channel中的數(shù)據(jù)將會(huì)得到兩個(gè)值,第一個(gè)值是從channel中讀取到的數(shù)據(jù),第二個(gè)值表示本次對(duì)channel進(jìn)行的讀操作是否成功,如果channel已關(guān)閉并且channel中沒(méi)有數(shù)據(jù)可讀,那么第二個(gè)值將會(huì)返回false,否則為true。

channel的遍歷方式

channel的遍歷方式

在Go中,可以通過(guò)for range循環(huán)的方式對(duì)channel中的元素進(jìn)行遍歷,其特點(diǎn)如下:

  • for range循環(huán)的每次迭代會(huì)從channel中讀取一個(gè)數(shù)據(jù),并將該值賦給指定的變量。
  • 如果channel中沒(méi)有數(shù)據(jù)可讀取,for range會(huì)阻塞等待,直到有數(shù)據(jù)可讀或channel關(guān)閉。
  • channel被關(guān)閉后,for range可以繼續(xù)從channel中讀取數(shù)據(jù),當(dāng)所有數(shù)據(jù)都被讀取后會(huì)自動(dòng)結(jié)束迭代。

使用案例如下:

package mainimport "fmt"func main() {charChan := make(chan int, 10)for i := 0; i < 10; i++ {charChan <- 'a' + i}close(charChan) // 關(guān)閉channelfor value := range charChan {fmt.Printf("read a char: %c\n", value)}
}

說(shuō)明一下:

  • 在對(duì)channel進(jìn)行讀操作時(shí),要確保有協(xié)程會(huì)對(duì)channel進(jìn)行對(duì)應(yīng)的寫操作,否則會(huì)造成死鎖(deadlock)。
  • 如果去掉上述代碼中關(guān)閉channel的操作,那么for range循環(huán)在讀取完channel中的數(shù)據(jù)后不會(huì)自動(dòng)結(jié)束迭代,而會(huì)繼續(xù)進(jìn)行讀操作,但此時(shí)沒(méi)有任何協(xié)程會(huì)再對(duì)該channel進(jìn)行寫操作,因此會(huì)造成死鎖(deadlock)。

只讀/只寫channel

只讀/只寫channel

在Go中,通過(guò)<-chan typechan<- type的方式,可以將channel聲明為只讀或只寫。如下:

package mainimport ("fmt""time"
)func WriteNum(intChan chan<- int) { // 只寫channelnum := 0for {intChan <- num // 向channel中寫入數(shù)據(jù)fmt.Printf("write a num: %d\n", num)num++}
}func ReadNum(intChan <-chan int) { // 只讀channelfor {time.Sleep(time.Second)num := <-intChan // 從channel中讀取數(shù)據(jù)fmt.Printf("read a num: %d\n", num)}
}func main() {intChan := make(chan int, 5)go WriteNum(intChan)ReadNum(intChan)
}

說(shuō)明一下:

  • 對(duì)只讀的channel進(jìn)行寫操作,或?qū)χ粚懙腸hannel進(jìn)行讀操作都會(huì)產(chǎn)生報(bào)錯(cuò)。
  • 由于WriteNum函數(shù)中只會(huì)對(duì)intChan進(jìn)行寫操作,而ReadNum函數(shù)中只會(huì)對(duì)intChan進(jìn)行讀操作,這時(shí)為了避免誤操作,可以分別將WriteNum和ReadNum函數(shù)的intChan參數(shù)聲明為只寫和只讀的channel。

channel最佳案例

題目要求:統(tǒng)計(jì)1-300000中有多少個(gè)素?cái)?shù)

為了快速統(tǒng)計(jì)出素?cái)?shù)的個(gè)數(shù),使用多個(gè)Go協(xié)程并發(fā)進(jìn)行素?cái)?shù)判斷,具體的解決思路如下:

  • 啟動(dòng)一個(gè)生產(chǎn)者協(xié)程,負(fù)責(zé)將1-300000的數(shù)字寫入到intChan中,作為數(shù)據(jù)源。
  • 啟動(dòng)多個(gè)消費(fèi)者協(xié)程,負(fù)責(zé)從intChan中讀取數(shù)據(jù)進(jìn)行素?cái)?shù)判斷,并將素?cái)?shù)寫入到primeChan中。
  • 主協(xié)程負(fù)責(zé)不斷讀取primeChan中的數(shù)據(jù),統(tǒng)計(jì)素?cái)?shù)的個(gè)數(shù)。

為了讓主協(xié)程能夠判斷primeChan中的素?cái)?shù)是否已經(jīng)讀取完畢,需要借助一個(gè)exitChan:

  • 生產(chǎn)者協(xié)程在生產(chǎn)完數(shù)據(jù)后關(guān)閉intChan,使得各個(gè)消費(fèi)者協(xié)程能夠判斷intChan中的數(shù)據(jù)是否消費(fèi)完畢,并在數(shù)據(jù)消費(fèi)完畢后寫入一個(gè)結(jié)束標(biāo)志到exitChan中。
  • 啟動(dòng)一個(gè)匿名協(xié)程,負(fù)責(zé)從exitChan中讀取結(jié)束標(biāo)志,當(dāng)讀取到的結(jié)束標(biāo)志個(gè)數(shù)等于消費(fèi)者的個(gè)數(shù)時(shí),表明所有消費(fèi)者協(xié)程已經(jīng)退出,這時(shí)關(guān)閉primeChan和exitChan。
  • 當(dāng)primeChan被關(guān)閉,并且primeChan中的數(shù)據(jù)已經(jīng)讀取完時(shí),則說(shuō)明所有素?cái)?shù)已經(jīng)統(tǒng)計(jì)完畢。

示意圖如下:

在這里插入圖片描述

代碼如下:

package mainimport "fmt"func Producer(numChan chan<- int) {for num := 1; num <= 300000; num++ {numChan <- num}close(numChan)
}func IsPrime(num int) bool {for i := 2; i <= num-1; i++ {if num%i == 0 {return false}}return true
}func Consumer(numChan <-chan int, primeChan chan<- int, exitChan chan<- bool) {for {num, ok := <-numChanif !ok {break}if IsPrime(num) {primeChan <- num}}exitChan <- true
}func main() {numChan := make(chan int, 300000)primeChan := make(chan int, 300000)exitChan := make(chan bool, 6)// 生產(chǎn)者協(xié)程go Producer(numChan)// 消費(fèi)者協(xié)程for i := 0; i < 6; i++ {go Consumer(numChan, primeChan, exitChan)}// 匿名協(xié)程go func() {for i := 0; i < 6; i++ {<-exitChan}close(primeChan)close(exitChan)}()// 主協(xié)程count := 0for {_, ok := <-primeChanif !ok {break}count++}fmt.Printf("prime count = %d\n", count) // prime count = 25998
}

select語(yǔ)句

select語(yǔ)句

在Go中,select語(yǔ)句用于實(shí)現(xiàn)非阻塞的通信。其特點(diǎn)如下:

  • select語(yǔ)句可以同時(shí)監(jiān)聽(tīng)多個(gè)channel的操作,它會(huì)選擇一個(gè)已經(jīng)就緒的操作,并執(zhí)行相應(yīng)的分支代碼。
  • 如果有多個(gè)操作就緒,select語(yǔ)句會(huì)隨機(jī)選擇其中一個(gè)操作執(zhí)行,如果沒(méi)有操作就緒,則會(huì)執(zhí)行default分支。

使用案例如下:

package mainimport "fmt"func main() {intChan := make(chan int, 10)stringChan := make(chan string, 10)for i := 0; i < 10; i++ {intChan <- istringChan <- fmt.Sprintf("hello select%d", i)}label:for {select {case num := <-intChan:fmt.Printf("read intChan: %d\n", num)case str := <-stringChan:fmt.Printf("read stringChan: %s\n", str)default:fmt.Printf("no data now...\n")break label}}
}

運(yùn)行代碼后可以看到,當(dāng)intChan和stringChan中都有數(shù)據(jù)時(shí),select語(yǔ)句會(huì)隨機(jī)對(duì)一個(gè)channel進(jìn)行讀操作,并在兩個(gè)channel中的數(shù)據(jù)都被讀取完后,通過(guò)執(zhí)行default分支中的break語(yǔ)句跳出for循環(huán)。運(yùn)行結(jié)果如下:

在這里插入圖片描述

http://www.risenshineclean.com/news/36087.html

相關(guān)文章:

  • 怎么做淘寶客網(wǎng)站賺錢百度指數(shù)官方
  • 網(wǎng)站黨組織規(guī)范化建設(shè)開(kāi)展情況石家莊seo推廣優(yōu)化
  • 網(wǎng)站域名實(shí)名制市場(chǎng)調(diào)研報(bào)告怎么寫
  • 網(wǎng)站中英文互譯 java怎么做html網(wǎng)頁(yè)制作模板代碼
  • 做海外房產(chǎn)最好的網(wǎng)站關(guān)鍵詞搜索量排名
  • 祥云縣外賣哪個(gè)網(wǎng)站清遠(yuǎn)頭條新聞
  • 舉報(bào)個(gè)人備案網(wǎng)站做經(jīng)營(yíng)性創(chuàng)建站點(diǎn)的步驟
  • 網(wǎng)站規(guī)劃與開(kāi)發(fā)設(shè)計(jì)汕頭網(wǎng)站建設(shè)技術(shù)外包
  • 網(wǎng)站如何建立品牌形象免費(fèi)開(kāi)源網(wǎng)站
  • 怎么做網(wǎng)站信息合肥網(wǎng)站優(yōu)化搜索
  • 怎樣做網(wǎng)站策劃網(wǎng)站收錄免費(fèi)咨詢
  • ps個(gè)人網(wǎng)站抖音視頻seo霸屏
  • 空間網(wǎng)站湖北短視頻搜索seo
  • 衡水企業(yè)網(wǎng)站巨量關(guān)鍵詞搜索查詢
  • 網(wǎng)站制作報(bào)價(jià)明細(xì)表bt磁力狗
  • 超市網(wǎng)站模版網(wǎng)絡(luò)推廣培訓(xùn)班
  • 鄭州網(wǎng)站推廣排名公司浙江關(guān)鍵詞優(yōu)化
  • 萬(wàn)網(wǎng)網(wǎng)站建設(shè)購(gòu)買過(guò)程汽車推廣軟文
  • 怎么自己的電腦做網(wǎng)站服務(wù)器百度網(wǎng)站是什么
  • 阿里巴巴怎么做公司網(wǎng)站我為什么不建議年輕人做銷售
  • 網(wǎng)站建設(shè) 資訊動(dòng)態(tài)電商軟文范例100字
  • 網(wǎng)站文化建設(shè)軟文新聞發(fā)布網(wǎng)站
  • 徐州企業(yè)網(wǎng)站設(shè)計(jì)免費(fèi)的網(wǎng)站推廣在線推廣
  • 歐美設(shè)計(jì)網(wǎng)站推薦百度推廣賬號(hào)怎么申請(qǐng)
  • 如何寫好網(wǎng)站開(kāi)發(fā)技術(shù)文檔頭條新聞今日頭條官方版本
  • 網(wǎng)站建設(shè)本科畢業(yè)設(shè)計(jì)論文鄭州網(wǎng)站推廣排名公司
  • 發(fā)果怎么做視頻網(wǎng)站四川省最新疫情情況
  • 一個(gè)網(wǎng)站怎么做鏡像站熱點(diǎn)事件
  • wordpress播放器源碼徐州seo外包
  • 動(dòng)態(tài)網(wǎng)站建設(shè)簡(jiǎn)介谷歌排名網(wǎng)站優(yōu)化