校园春色亚洲色图_亚洲视频分类_中文字幕精品一区二区精品_麻豆一区区三区四区产品精品蜜桃

主頁 > 知識(shí)庫 > Golang 之協(xié)程的用法講解

Golang 之協(xié)程的用法講解

熱門標(biāo)簽:涿州代理外呼系統(tǒng) 阿克蘇地圖標(biāo)注 電話機(jī)器人軟件免費(fèi) 評(píng)價(jià)高的400電話辦理 百度地圖標(biāo)注后傳給手機(jī) 壽光微信地圖標(biāo)注 外呼系統(tǒng)用什么卡 excel地圖標(biāo)注分布數(shù)據(jù) 外呼系統(tǒng)顯本地手機(jī)號(hào)

一、Golang 線程和協(xié)程的區(qū)別  

備注:需要區(qū)分進(jìn)程、線程(內(nèi)核級(jí)線程)、協(xié)程(用戶級(jí)線程)三個(gè)概念。

進(jìn)程、線程 和 協(xié)程 之間概念的區(qū)別

對(duì)于 進(jìn)程、線程,都是有內(nèi)核進(jìn)行調(diào)度,有 CPU 時(shí)間片的概念,進(jìn)行 搶占式調(diào)度(有多種調(diào)度算法)

對(duì)于 協(xié)程(用戶級(jí)線程),這是對(duì)內(nèi)核透明的,也就是系統(tǒng)并不知道有協(xié)程的存在,是完全由用戶自己的程序進(jìn)行調(diào)度的,因?yàn)槭怯捎脩舫绦蜃约嚎刂?,那么就很難像搶占式調(diào)度那樣做到強(qiáng)制的 CPU 控制權(quán)切換到其他進(jìn)程/線程,通常只能進(jìn)行 協(xié)作式調(diào)度,需要協(xié)程自己主動(dòng)把控制權(quán)轉(zhuǎn)讓出去之后,其他協(xié)程才能被執(zhí)行到。

goroutine 和協(xié)程區(qū)別

本質(zhì)上,goroutine 就是協(xié)程。 不同的是,Golang 在 runtime、系統(tǒng)調(diào)用等多方面對(duì) goroutine 調(diào)度進(jìn)行了封裝和處理,當(dāng)遇到長(zhǎng)時(shí)間執(zhí)行或者進(jìn)行系統(tǒng)調(diào)用時(shí),會(huì)主動(dòng)把當(dāng)前 goroutine 的CPU (P) 轉(zhuǎn)讓出去,讓其他 goroutine 能被調(diào)度并執(zhí)行,也就是 Golang 從語言層面支持了協(xié)程。

Golang 的一大特色就是從語言層面原生支持協(xié)程,在函數(shù)或者方法前面加 go關(guān)鍵字就可創(chuàng)建一個(gè)協(xié)程。

其他方面的比較

1. 內(nèi)存消耗方面

每個(gè) goroutine (協(xié)程) 默認(rèn)占用內(nèi)存遠(yuǎn)比 Java 、C 的線程少?!   ?/p>

goroutine:2KB     

線程:8MB

2. 線程和 goroutine 切換調(diào)度開銷方面

線程/goroutine 切換開銷方面,goroutine 遠(yuǎn)比線程小    

線程:涉及模式切換(從用戶態(tài)切換到內(nèi)核態(tài))、16個(gè)寄存器、PC、SP...等寄存器的刷新等。    

goroutine:只有三個(gè)寄存器的值修改 - PC / SP / DX.

二、協(xié)程底層實(shí)現(xiàn)原理  

線程是操作系統(tǒng)的內(nèi)核對(duì)象,多線程編程時(shí),如果線程數(shù)過多,就會(huì)導(dǎo)致頻繁的上下文切換,這些 cpu 時(shí)間是一個(gè)額外的耗費(fèi)。

所以在一些高并發(fā)的網(wǎng)絡(luò)服務(wù)器編程中,使用一個(gè)線程服務(wù)一個(gè) socket 連接是很不明智的。于是操作系統(tǒng)提供了基于事件模式的異步編程模型。用少量的線程來服務(wù)大量的網(wǎng)絡(luò)連接和I/O操作。

但是采用異步和基于事件的編程模型,復(fù)雜化了程序代碼的編寫,非常容易出錯(cuò)。因?yàn)榫€程穿插,也提高排查錯(cuò)誤的難度。

協(xié)程,是在應(yīng)用層模擬的線程,他避免了上下文切換的額外耗費(fèi),兼顧了多線程的優(yōu)點(diǎn)。簡(jiǎn)化了高并發(fā)程序的復(fù)雜度。舉個(gè)例子,一個(gè)高并發(fā)的網(wǎng)絡(luò)服務(wù)器,每一個(gè)socket連接進(jìn)來,服務(wù)器用一個(gè)協(xié)程來對(duì)他進(jìn)行服務(wù)。代碼非常清晰。而且兼顧了性能。

那么,協(xié)程是怎么實(shí)現(xiàn)的呢?

他和線程的原理是一樣的,當(dāng) a線程 切換到 b線程 的時(shí)候,需要將 a線程 的相關(guān)執(zhí)行進(jìn)度壓入棧,然后將 b線程 的執(zhí)行進(jìn)度出棧,進(jìn)入 b線程 的執(zhí)行序列。協(xié)程只不過是在 應(yīng)用層 實(shí)現(xiàn)這一點(diǎn)。但是,協(xié)程并不是由操作系統(tǒng)調(diào)度的,而且應(yīng)用程序也沒有能力和權(quán)限執(zhí)行 cpu 調(diào)度。怎么解決這個(gè)問題?

答案是,協(xié)程是基于線程的。內(nèi)部實(shí)現(xiàn)上,維護(hù)了一組數(shù)據(jù)結(jié)構(gòu)和 n 個(gè)線程,真正的執(zhí)行還是線程,協(xié)程執(zhí)行的代碼被扔進(jìn)一個(gè)待執(zhí)行隊(duì)列中,由這 n 個(gè)線程從隊(duì)列中拉出來執(zhí)行。這就解決了協(xié)程的執(zhí)行問題。那么協(xié)程是怎么切換的呢?答案是:golang 對(duì)各種 io函數(shù) 進(jìn)行了封裝,這些封裝的函數(shù)提供給應(yīng)用程序使用,而其內(nèi)部調(diào)用了操作系統(tǒng)的異步 io函數(shù),當(dāng)這些異步函數(shù)返回 busy 或 bloking 時(shí),golang 利用這個(gè)時(shí)機(jī)將現(xiàn)有的執(zhí)行序列壓棧,讓線程去拉另外一個(gè)協(xié)程的代碼來執(zhí)行,基本原理就是這樣,利用并封裝了操作系統(tǒng)的異步函數(shù)。包括 linux 的 epoll、select 和 windows 的 iocp、event 等。

由于golang是從編譯器和語言基礎(chǔ)庫多個(gè)層面對(duì)協(xié)程做了實(shí)現(xiàn),所以,golang的協(xié)程是目前各類有協(xié)程概念的語言中實(shí)現(xiàn)的最完整和成熟的。十萬個(gè)協(xié)程同時(shí)運(yùn)行也毫無壓力。關(guān)鍵我們不會(huì)這么寫代碼。但是總體而言,程序員可以在編寫 golang 代碼的時(shí)候,可以更多的關(guān)注業(yè)務(wù)邏輯的實(shí)現(xiàn),更少的在這些關(guān)鍵的基礎(chǔ)構(gòu)件上耗費(fèi)太多精力。

三、協(xié)程的歷史以及特點(diǎn)  

協(xié)程(Coroutine)是在1963年由Melvin E. Conway USAF, Bedford, MA等人提出的一個(gè)概念。而且協(xié)程的概念是早于線程(Thread)提出的。但是由于協(xié)程是非搶占式的調(diào)度,無法實(shí)現(xiàn)公平的任務(wù)調(diào)用。也無法直接利用多核優(yōu)勢(shì)。因此,我們不能武斷地說協(xié)程是比線程更高級(jí)的技術(shù)。

盡管,在任務(wù)調(diào)度上,協(xié)程是弱于線程的。但是在資源消耗上,協(xié)程則是極低的。一個(gè)線程的內(nèi)存在 MB 級(jí)別,而協(xié)程只需要 KB 級(jí)別。而且線程的調(diào)度需要內(nèi)核態(tài)與用戶的頻繁切入切出,資源消耗也不小。

我們把協(xié)程的基本特點(diǎn)歸納為:

1. 協(xié)程調(diào)度機(jī)制無法實(shí)現(xiàn)公平調(diào)度

2. 協(xié)程的資源開銷是非常低的,一臺(tái)普通的服務(wù)器就可以支持百萬協(xié)程。   

那么,近幾年為何協(xié)程的概念可以大熱。我認(rèn)為一個(gè)特殊的場(chǎng)景使得協(xié)程能夠廣泛的發(fā)揮其優(yōu)勢(shì),并且屏蔽掉了劣勢(shì) --> 網(wǎng)絡(luò)編程。與一般的計(jì)算機(jī)程序相比,網(wǎng)絡(luò)編程有其獨(dú)有的特點(diǎn)。

1. 高并發(fā)(每秒鐘上千數(shù)萬的單機(jī)訪問量)

2. Request/Response。程序生命期端(毫秒,秒級(jí))

3. 高IO,低計(jì)算(連接數(shù)據(jù)庫,請(qǐng)求API)。   

最開始的網(wǎng)絡(luò)程序其實(shí)就是一個(gè)線程一個(gè)請(qǐng)求設(shè)計(jì)的(Apache)。后來,隨著網(wǎng)絡(luò)的普及,誕生了C10K問題。Nginx 通過單線程異步 IO 把網(wǎng)絡(luò)程序的執(zhí)行流程進(jìn)行了亂序化,通過 IO 事件機(jī)制最大化的保證了CPU的利用率。

至此,現(xiàn)代網(wǎng)絡(luò)程序的架構(gòu)已經(jīng)形成?;贗O事件調(diào)度的異步編程。其代表作恐怕就屬 NodeJS 了吧。

異步編程的槽點(diǎn)

異步編程為了追求程序的性能,強(qiáng)行的將線性的程序打亂,程序變得非常的混亂與復(fù)雜。對(duì)程序狀態(tài)的管理也變得異常困難。寫過Nginx C Module的同學(xué)應(yīng)該知道我說的是什么。我們開始吐槽 NodeJS 那惡心的層層Callback。

Golang   

在我們瘋狂被 NodeJS 的層層回調(diào)惡心到的時(shí)候,Golang 作為名門之后開始走入我們的視野。并且迅速的在Web后端極速的跑馬圈地。其代表者 Docker 以及圍繞這 Docker 展開的整個(gè)容器生態(tài)圈欣欣向榮起來。其最大的賣點(diǎn) – 協(xié)程 開始真正的流行與討論起來。

我們開始向?qū)慞HP一樣來寫全異步IO的程序。看上去美好極了,仿佛世界就是這樣了。

在網(wǎng)絡(luò)編程中,我們可以理解為 Golang 的協(xié)程本質(zhì)上其實(shí)就是對(duì) IO 事件的封裝,并且通過語言級(jí)的支持讓異步的代碼看上去像同步執(zhí)行的一樣。

四、Golang 協(xié)程的應(yīng)用  

我們知道,協(xié)程(coroutine)是Go語言中的輕量級(jí)線程實(shí)現(xiàn),由Go運(yùn)行時(shí)(runtime)管理。

在一個(gè)函數(shù)調(diào)用前加上go關(guān)鍵字,這次調(diào)用就會(huì)在一個(gè)新的goroutine中并發(fā)執(zhí)行。當(dāng)被調(diào)用的函數(shù)返回時(shí),這個(gè)goroutine也自動(dòng)結(jié)束。需要注意的是,如果這個(gè)函數(shù)有返回值,那么這個(gè)返回值會(huì)被丟棄。

先看一下下面的程序代碼:

func Add(x, y int) {
    z := x + y
    fmt.Println(z)
}
 
func main() {
    for i:=0; i10; i++ {
        go Add(i, i)
    }
}

執(zhí)行上面的代碼,會(huì)發(fā)現(xiàn)屏幕什么也沒打印出來,程序就退出了?! ?/p>

對(duì)于上面的例子,main()函數(shù)啟動(dòng)了10個(gè)goroutine,然后返回,這時(shí)程序就退出了,而被啟動(dòng)的執(zhí)行 Add() 的 goroutine 沒來得及執(zhí)行。我們想要讓 main() 函數(shù)等待所有 goroutine 退出后再返回,但如何知道 goroutine 都退出了呢?這就引出了多個(gè)goroutine之間通信的問題。

在工程上,有兩種最常見的并發(fā)通信模型:共享內(nèi)存 和 消息。

下面的例子,使用了鎖變量(屬于一種共享內(nèi)存)來同步協(xié)程,事實(shí)上 Go 語言主要使用消息機(jī)制(channel)來作為通信模型

package main 
import (
    "fmt"
    "sync"
    "runtime"
)
 
var counter int = 0 
func Count(lock *sync.Mutex) {
    lock.Lock() // 上鎖
    counter++
    fmt.Println("counter =", counter)
    lock.Unlock()   // 解鎖
}
 
func main() {
    lock := sync.Mutex{}
 
    for i:=0; i10; i++ {
        go Count(lock)
    }
    for {
        lock.Lock() // 上鎖
        c := counter
        lock.Unlock()   // 解鎖
 
        runtime.Gosched() // 出讓時(shí)間片
 
        if c >= 10 {
            break
        }
    }
}

channel

消息機(jī)制認(rèn)為每個(gè)并發(fā)單元是自包含的、獨(dú)立的個(gè)體,并且都有自己的變量,但在不同并發(fā)單元間這些變量不共享。每個(gè)并發(fā)單元的輸入和輸出只有一種,那就是消息。

channel 是 Go 語言在語言級(jí)別提供的 goroutine 間的通信方式,我們可以使用 channel 在多個(gè) goroutine 之間傳遞消息。channel是進(jìn)程內(nèi)的通信方式,因此通過 channel 傳遞對(duì)象的過程和調(diào)用函數(shù)時(shí)的參數(shù)傳遞行為比較一致,比如也可以傳遞指針等。channel 是類型相關(guān)的,一個(gè) channel 只能傳遞一種類型的值,這個(gè)類型需要在聲明 channel 時(shí)指定。

channel的聲明形式為:

var chanName chan ElementType

舉個(gè)例子,聲明一個(gè)傳遞int類型的channel:

var ch chan int

使用內(nèi)置函數(shù) make() 定義一個(gè)channel:

ch := make(chan int)

在channel的用法中,最常見的包括寫入和讀出:

// 將一個(gè)數(shù)據(jù)value寫入至channel,這會(huì)導(dǎo)致阻塞,直到有其他goroutine從這個(gè)channel中讀取數(shù)據(jù)
ch - value
// 從channel中讀取數(shù)據(jù),如果channel之前沒有寫入數(shù)據(jù),也會(huì)導(dǎo)致阻塞,直到channel中被寫入數(shù)據(jù)為止
value := -ch

默認(rèn)情況下,channel的接收和發(fā)送都是阻塞的,除非另一端已準(zhǔn)備好。

我們還可以創(chuàng)建一個(gè)帶緩沖的channel:

c := make(chan int, 1024)
// 從帶緩沖的channel中讀數(shù)據(jù)
for i:=range c {
  ...
}

此時(shí),創(chuàng)建一個(gè)大小為1024的int類型的channel,即使沒有讀取方,寫入方也可以一直往channel里寫入,在緩沖區(qū)被填完之前都不會(huì)阻塞。

可以關(guān)閉不再使用的channel:

close(ch)

應(yīng)該在生產(chǎn)者的地方關(guān)閉channel,如果在消費(fèi)者的地方關(guān)閉,容易引起panic;

現(xiàn)在利用channel來重寫上面的例子:

func Count(ch chan int) {
    ch - 1
    fmt.Println("Counting")
}
 
func main() {
 
    chs := make([] chan int, 10)
 
    for i:=0; i10; i++ {
        chs[i] = make(chan int)
        go Count(chs[i])
    }
 
    for _, ch := range(chs) {
        -ch
    }
}

在這個(gè)例子中,定義了一個(gè)包含10個(gè)channel的數(shù)組,并把數(shù)組中的每個(gè)channel分配給10個(gè)不同的goroutine。在每個(gè)goroutine完成后,向goroutine寫入一個(gè)數(shù)據(jù),在這個(gè)channel被讀取前,這個(gè)操作是阻塞的。

在所有的goroutine啟動(dòng)完成后,依次從10個(gè)channel中讀取數(shù)據(jù),在對(duì)應(yīng)的channel寫入數(shù)據(jù)前,這個(gè)操作也是阻塞的。

這樣,就用channel實(shí)現(xiàn)了類似鎖的功能,并保證了所有g(shù)oroutine完成后main()才返回。

另外,我們?cè)趯⒁粋€(gè)channel變量傳遞到一個(gè)函數(shù)時(shí),可以通過將其指定為單向channel變量,從而限制該函數(shù)中可以對(duì)此channel的操作。

select

在UNIX中,select()函數(shù)用來監(jiān)控一組描述符,該機(jī)制常被用于實(shí)現(xiàn)高并發(fā)的socket服務(wù)器程序。Go語言直接在語言級(jí)別支持select關(guān)鍵字,用于處理異步IO問題,大致結(jié)構(gòu)如下:

select {
    case - chan1:
    // 如果chan1成功讀到數(shù)據(jù)
     
    case chan2 - 1:
    // 如果成功向chan2寫入數(shù)據(jù)
 
    default:
    // 默認(rèn)分支
}

select默認(rèn)是阻塞的,只有當(dāng)監(jiān)聽的channel中有發(fā)送或接收可以進(jìn)行時(shí)才會(huì)運(yùn)行,當(dāng)多個(gè)channel都準(zhǔn)備好的時(shí)候,select是隨機(jī)的選擇一個(gè)執(zhí)行的。

Go語言沒有對(duì)channel提供直接的超時(shí)處理機(jī)制,但我們可以利用select來間接實(shí)現(xiàn),例如:

timeout := make(chan bool, 1) 
go func() {
    time.Sleep(1e9)
    timeout - true
}()
 
switch {
    case - ch:
    // 從ch中讀取到數(shù)據(jù)
 
    case - timeout:
    // 沒有從ch中讀取到數(shù)據(jù),但從timeout中讀取到了數(shù)據(jù)
}

這樣使用select就可以避免永久等待的問題,因?yàn)槌绦驎?huì)在timeout中獲取到一個(gè)數(shù)據(jù)后繼續(xù)執(zhí)行,而無論對(duì)ch的讀取是否還處于等待狀態(tài)。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。

您可能感興趣的文章:
  • Go 并發(fā)實(shí)現(xiàn)協(xié)程同步的多種解決方法
  • go等待一組協(xié)程結(jié)束的操作方式
  • golang協(xié)程池模擬實(shí)現(xiàn)群發(fā)郵件功能
  • 解決go在函數(shù)退出后子協(xié)程的退出問題
  • Go使用協(xié)程交替打印字符
  • go 協(xié)程返回值處理操作
  • 淺談golang for 循環(huán)中使用協(xié)程的問題
  • Go并發(fā):使用sync.WaitGroup實(shí)現(xiàn)協(xié)程同步方式

標(biāo)簽:蘭州 雞西 汕頭 梅河口 吐魯番 重慶 銅川 欽州

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Golang 之協(xié)程的用法講解》,本文關(guān)鍵詞  Golang,之協(xié),程,的,用法,講解,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Golang 之協(xié)程的用法講解》相關(guān)的同類信息!
  • 本頁收集關(guān)于Golang 之協(xié)程的用法講解的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    主站蜘蛛池模板: 永川市| 徐闻县| 霸州市| 枣阳市| 红桥区| 肃宁县| 田林县| 门源| 韩城市| 象山县| 玛沁县| 蓝田县| 迁安市| 旬邑县| 邹城市| 仪陇县| 鹤岗市| 满洲里市| 吴江市| 会宁县| 景宁| 灵宝市| 穆棱市| 临湘市| 育儿| 垣曲县| 靖江市| 英超| 雅安市| 日土县| 镇雄县| 遂宁市| 木兰县| 河北区| 如东县| 略阳县| 安新县| 建平县| 彭州市| 双桥区| 灵台县|