最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Go語言并發(fā)編程:goroutine、channel、select、sync、原子操作

2022-10-27 23:33 作者:苦茶今天斷更了嗎  | 我要投稿

Go語言并發(fā)編程:goroutine、channel、select、sync、原子操作

并發(fā)并行、goroutine、channel的內(nèi)容看之前的筆記

?

Go語言中的操作系統(tǒng)線程和goroutine的關(guān)系:

①一個(gè)操作系統(tǒng)線程對應(yīng)用戶態(tài)多個(gè)goroutine。

②go程序可以同時(shí)使用多個(gè)操作系統(tǒng)線程。

③goroutine和OS線程是多對多的關(guān)系,即m:n。

Runtime包

runtime.Gosched( ):讓出CPU時(shí)間片,重新等待安排任務(wù)。


runtime.Goexit(?)退出當(dāng)前協(xié)程。


runtime.GOMAXPROCS

Go運(yùn)行時(shí)的調(diào)度器使用GOMAXPROCS參數(shù)來確定需要使用多少個(gè)OS線程來同時(shí)執(zhí)行Go代碼。默認(rèn)值是機(jī)器上的CPU核心數(shù)。(GOMAXPROCS是m:n調(diào)度中的n)。

Go語言中可以通過runtime.GOMAXPROCS(?)函數(shù)設(shè)置當(dāng)前程序并發(fā)時(shí)占用的CPU邏輯核心數(shù)。

Go1.5版本之前,默認(rèn)使用的是單核心執(zhí)行。Go1.5版本之后,默認(rèn)使用全部的CPU邏輯核心數(shù)。

可以通過將任務(wù)分配到不同的CPU邏輯核心上實(shí)現(xiàn)并行的效果,舉個(gè)例子:


無緩沖的通道(阻塞的通道、同步通道):


為什么會出現(xiàn)deadlock錯(cuò)誤呢?

因?yàn)槭褂?strong>ch := make(chan int)創(chuàng)建的是無緩沖的通道,無緩沖的通道必須有接收才能發(fā)送。

?

上面的代碼會阻塞在ch <- 10這一行代碼形成死鎖,那如何解決這個(gè)問題呢?

一種方法是啟用一個(gè)goroutine去接收值,例如:


?

無緩沖通道上的發(fā)送操作會阻塞,直到另一個(gè)goroutine在該通道上執(zhí)行接收操作,這時(shí)值才能發(fā)送成功,兩個(gè)goroutine將繼續(xù)執(zhí)行。相反,如果接收操作先執(zhí)行,接收方的goroutine將阻塞,直到另一個(gè)goroutine在該通道上發(fā)送一個(gè)值。

?

使用無緩沖通道進(jìn)行通信將導(dǎo)致發(fā)送和接收的goroutine同步化。因此,無緩沖通道也被稱為同步通道。

?

有緩沖的通道:

可以在使用make函數(shù)初始化通道的時(shí)候?yàn)槠渲付ㄍǖ赖娜萘?,例如?/p>

只要通道的容量大于零,那么該通道就是有緩沖的通道,通道的容量表示通道中能存放元素的數(shù)量

可以使用內(nèi)置的len函數(shù)獲取通道內(nèi)元素的數(shù)量,使用cap函數(shù)獲取通道的容量。

?

?close()

可以通過內(nèi)置的close()函數(shù)關(guān)閉channel(如果你的管道不往里存值或者取值的時(shí)候,一定記得關(guān)閉管道)

當(dāng)通過通道發(fā)送有限的數(shù)據(jù)時(shí),我們可以通過close函數(shù)關(guān)閉通道來告知從該通道接收值的goroutine停止等待。當(dāng)通道被關(guān)閉時(shí),往該通道發(fā)送值會引發(fā)panic,從該通道里接收的值一直都是類型零值。那如何判斷一個(gè)通道是否被關(guān)閉了呢?

?單向通道:

有的時(shí)候我們會將通道作為參數(shù)在多個(gè)任務(wù)函數(shù)間傳遞,很多時(shí)候我們在不同的任務(wù)函數(shù)中使用通道都會對其進(jìn)行限制,比如限制通道在函數(shù)中只能發(fā)送或只能接收。

?

Go語言中提供了單向通道來處理這種情況。例如,我們把上面的例子改造如下:

chan<-?int是一個(gè)只能發(fā)送(只寫)的通道,可以發(fā)送但是不能接收;

<-chan int是一個(gè)只能接收(只讀)的通道,可以接收但是不能發(fā)送;


?worker pool(goroutine池)

本質(zhì)上是生產(chǎn)者消費(fèi)者模型

可以有效控制goroutine數(shù)量,防止暴漲

需求:

計(jì)算一個(gè)數(shù)字的各個(gè)位數(shù)之和,例如數(shù)字123,結(jié)果為1+2+3=6

隨機(jī)生成數(shù)字進(jìn)行計(jì)算

定時(shí)器:

Timer:時(shí)間到了,執(zhí)行只執(zhí)行1次

Ticker:時(shí)間到了,多次執(zhí)行。

?


Select:

select多路復(fù)用:

在某些場景下我們需要同時(shí)從多個(gè)通道接收數(shù)據(jù)。通道在接收數(shù)據(jù)時(shí),如果沒有數(shù)據(jù)可以接收將會發(fā)生阻塞。

這種方式雖然可以實(shí)現(xiàn)從多個(gè)通道接收值的需求,但是運(yùn)行性能會差很多。為了應(yīng)對這種場景,Go內(nèi)置了select關(guān)鍵字,可以同時(shí)響應(yīng)多個(gè)通道的操作。

select的使用類似于switch語句,它有一系列case分支和一個(gè)默認(rèn)的分支。每個(gè)case會對應(yīng)一個(gè)通道的通信(接收或發(fā)送)過程。select會一直等待,直到某個(gè)case的通信操作完成時(shí),就會執(zhí)行case分支對應(yīng)的語句。具體格式如下:

select可以同時(shí)監(jiān)聽一個(gè)或多個(gè)channel,直到其中一個(gè)channel ready:

如果多個(gè)channel同時(shí)ready,則隨機(jī)選擇一個(gè)執(zhí)行:

?可以用于判斷管道是否存滿:


并發(fā)安全和鎖:

有時(shí)候在Go代碼中可能會存在多個(gè)goroutine同時(shí)操作一個(gè)資源(臨界區(qū)),這種情況會發(fā)生競態(tài)問題(數(shù)據(jù)競態(tài))。


?

上面的代碼中我們開啟了兩個(gè)goroutine去累加變量x的值,這兩個(gè)goroutine在訪問和修改x變量的時(shí)候就會存在數(shù)據(jù)競爭,導(dǎo)致最后的結(jié)果與期待的不符。

互斥鎖

互斥鎖是一種常用的控制共享資源訪問的方法,它能夠保證同時(shí)只有一個(gè)goroutine可以訪問共享資源。Go語言中使用sync包的Mutex類型來實(shí)現(xiàn)互斥鎖。 使用互斥鎖來修復(fù)上面代碼的問題:

使用互斥鎖能夠保證同一時(shí)間有且只有一個(gè)goroutine進(jìn)入臨界區(qū),其他的goroutine則在等待鎖;當(dāng)互斥鎖釋放后,等待的goroutine才可以獲取鎖進(jìn)入臨界區(qū),多個(gè)goroutine同時(shí)等待一個(gè)鎖時(shí),喚醒的策略是隨機(jī)的。

?

讀寫互斥鎖

互斥鎖是完全互斥的,但是有很多實(shí)際的場景下是讀多寫少的,當(dāng)我們并發(fā)的去讀取一個(gè)資源不涉及資源修改的時(shí)候是沒有必要加鎖的,這種場景下使用讀寫鎖是更好的一種選擇。 讀寫鎖在Go語言中使用sync包中的RWMutex類型。

?

讀寫鎖分為兩種:讀鎖和寫鎖。當(dāng)一個(gè)goroutine獲取讀鎖之后,其他的goroutine如果是獲取讀鎖會繼續(xù)獲得鎖,如果是獲取寫鎖就會等待;當(dāng)一個(gè)goroutine獲取寫鎖之后,其他的goroutine無論是獲取讀鎖還是寫鎖都會等待。


Sync:

sync.WaitGroup:

在代碼中生硬的使用time.Sleep肯定是不合適的,Go語言中可以使用sync.WaitGroup來實(shí)現(xiàn)并發(fā)任務(wù)的同步。

sync.WaitGroup是一個(gè)結(jié)構(gòu)體,傳遞的時(shí)候要傳遞指針。

sync.WaitGroup有以下幾個(gè)方法:

sync.WaitGroup內(nèi)部維護(hù)著一個(gè)計(jì)數(shù)器,計(jì)數(shù)器的值可以增加和減少。例如當(dāng)我們啟動了N 個(gè)并發(fā)任務(wù)時(shí),就將計(jì)數(shù)器值增加N。每個(gè)任務(wù)完成時(shí)通過調(diào)用Done()方法將計(jì)數(shù)器減1。通過調(diào)用Wait()來等待并發(fā)任務(wù)執(zhí)行完,當(dāng)計(jì)數(shù)器值為0時(shí),表示所有并發(fā)任務(wù)已經(jīng)完成。

?

sync.Once

在編程的很多場景下我們需要確保某些操作在高并發(fā)的場景下只執(zhí)行一次,例如只加載一次配置文件、只關(guān)閉一次通道等。

?

sync.Once只有一個(gè)Do方法,其簽名:func (o *Once) Do(f func()) {}

注意:如果要執(zhí)行的函數(shù)f需要傳遞參數(shù)就需要搭配閉包來使用。

?

加載配置文件示例

延遲一個(gè)開銷很大的初始化操作到真正用到它的時(shí)候再執(zhí)行是一個(gè)很好的實(shí)踐。因?yàn)轭A(yù)先初始化一個(gè)變量(比如在init函數(shù)中完成初始化)會增加程序的啟動耗時(shí),而且有可能實(shí)際執(zhí)行過程中這個(gè)變量沒有用上,那么這個(gè)初始化操作就不是必須要做的??匆粋€(gè)例子:

多個(gè)goroutine并發(fā)調(diào)用Icon函數(shù)時(shí)不是并發(fā)安全的,現(xiàn)代的編譯器和CPU可能會在保證每個(gè)goroutine都滿足串行一致的基礎(chǔ)上自由地重排訪問內(nèi)存的順序。loadIcons函數(shù)可能會被重排為以下結(jié)果:

在這種情況下就會出現(xiàn)即使判斷了icons不是nil也不意味著變量初始化完成了??紤]到這種情況,我們能想到的辦法就是添加互斥鎖,保證初始化icons的時(shí)候不會被其他的goroutine操作,但是這樣做又會引發(fā)性能問題。使用sync.Once改造的示例代碼如下:

?sync.Once其實(shí)內(nèi)部包含一個(gè)互斥鎖和一個(gè)布爾值,互斥鎖保證布爾值和數(shù)據(jù)的安全,而布爾值用來記錄初始化是否完成。這樣設(shè)計(jì)就能保證初始化操作的時(shí)候是并發(fā)安全的,并且初始化操作也不會被執(zhí)行多次。

?

sync.MapGo語言中內(nèi)置的map不是并發(fā)安全的。

上面的代碼開啟少量幾個(gè)goroutine的時(shí)候可能沒什么問題,當(dāng)并發(fā)多了之后執(zhí)行上面的代碼就會報(bào)fatal error: concurrent map writes錯(cuò)誤。

像這種場景下就需要為map加鎖來保證并發(fā)的安全性了,Go語言的sync包中提供了一個(gè)開箱即用的并發(fā)安全版map–sync.Map。開箱即用表示不用像內(nèi)置的map一樣使用make函數(shù)初始化就能直接使用。同時(shí)sync.Map內(nèi)置了諸如Store、Load、LoadOrStore、Delete、Range等操作方法。

原子操作(atomic包):

代碼中的加鎖操作因?yàn)樯婕皟?nèi)核態(tài)的上下文切換會比較耗時(shí)、代價(jià)比較高。針對基本數(shù)據(jù)類型我們還可以使用原子操作來保證并發(fā)安全,因?yàn)樵硬僮魇荊o語言提供的方法它在用戶態(tài)就可以完成,因此性能比加鎖操作更好。Go語言中原子操作由內(nèi)置的標(biāo)準(zhǔn)庫sync/atomic提供。

atomic包提供了底層的原子級內(nèi)存操作,對于同步算法的實(shí)現(xiàn)很有用。這些函數(shù)必須謹(jǐn)慎地保證正確使用。除了某些特殊的底層應(yīng)用,使用通道或者sync包的函數(shù)/類型實(shí)現(xiàn)同步更好。


?

?

?

?

?


Go語言并發(fā)編程:goroutine、channel、select、sync、原子操作的評論 (共 條)

分享到微博請遵守國家法律
阳曲县| 阳江市| 乡城县| 含山县| 马鞍山市| 八宿县| 武义县| 仙游县| 遂川县| 莒南县| 全椒县| 丽水市| 林甸县| 丹东市| 临颍县| 洪泽县| 阳泉市| 汶上县| 龙江县| 棋牌| 吕梁市| 青州市| 呼和浩特市| 余江县| 浦江县| 襄樊市| 达拉特旗| 达孜县| 定结县| 工布江达县| 苏尼特右旗| 岱山县| 唐河县| 天祝| 汨罗市| 顺义区| 潼关县| 华蓥市| 郓城县| 吴堡县| 江门市|