自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何使用 Go 更好地開(kāi)發(fā)并發(fā)程序

開(kāi)發(fā) 前端
Go 語(yǔ)言的并發(fā)特性是其一大亮點(diǎn),今天我們來(lái)帶著大家一起看看如何使用 Go 更好地開(kāi)發(fā)并發(fā)程序?

 Go 語(yǔ)言的并發(fā)特性是其一大亮點(diǎn),今天我們來(lái)帶著大家一起看看如何使用 Go 更好地開(kāi)發(fā)并發(fā)程序?

我們都知道計(jì)算機(jī)的核心為 CPU,它是計(jì)算機(jī)的運(yùn)算和控制核心,承載了所有的計(jì)算任務(wù)。最近半個(gè)世紀(jì)以來(lái),由于半導(dǎo)體技術(shù)的高速發(fā)展,集成電路中晶體管的數(shù)量也在大幅度增長(zhǎng),這大大提升了 CPU 的性能。著名的摩爾定律——“集成電路芯片上所集成的電路的數(shù)目,每隔18個(gè)月就翻一番”,描述的就是該種情形。

過(guò)于密集的晶體管雖然提高了 CPU 的處理性能,但也帶來(lái)了單個(gè)芯片發(fā)熱過(guò)高和成本過(guò)高的問(wèn)題,與此同時(shí),受限于材料技術(shù)的發(fā)展,芯片中晶體管數(shù)量密度的增加速度已經(jīng)放緩。也就是說(shuō),程序已經(jīng)無(wú)法簡(jiǎn)單地依賴(lài)硬件的提升而提升運(yùn)行速度。這時(shí),多核 CPU 的出現(xiàn)讓我們看到了提升程序運(yùn)行速度的另一個(gè)方向:將程序的執(zhí)行過(guò)程分為多個(gè)可并行或并發(fā)執(zhí)行的步驟,讓它們分別在不同的 CPU 核心中同時(shí)執(zhí)行,最后將各部分的執(zhí)行結(jié)果進(jìn)行合并得到最終結(jié)果。

并行和并發(fā)是計(jì)算機(jī)程序執(zhí)行的常見(jiàn)概念,它們的區(qū)別在于:

  • 并行 ,指兩個(gè)或多個(gè)程序在 同一個(gè)時(shí)刻 執(zhí)行;
  • 并發(fā) ,指兩個(gè)或多個(gè)程序在 同一個(gè)時(shí)間段內(nèi) 執(zhí)行。

并行執(zhí)行的程序,無(wú)論從宏觀還是微觀的角度觀察,同一時(shí)刻內(nèi)都有多個(gè)程序在 CPU 中執(zhí)行。這就要求 CPU 提供多核計(jì)算能力,多個(gè)程序被分配到 CPU 的不同的核中被同時(shí)執(zhí)行。

而 并發(fā)執(zhí)行的程序 ,僅需要在宏觀角度觀察到多個(gè)程序在 CPU 中同時(shí)執(zhí)行。即使是單核 CPU 也可以通過(guò)分時(shí)復(fù)用的方式,給多個(gè)程序分配一定的執(zhí)行時(shí)間片,讓它們?cè)?CPU 上被快速輪換執(zhí)行,從而在宏觀上模擬出多個(gè)程序同時(shí)執(zhí)行的效果。但從微觀角度來(lái)看,這些程序其實(shí)是在 CPU 中被串行執(zhí)行。

Go 的 MPG 線程模型

Go 被認(rèn)為是一門(mén)高性能并發(fā)語(yǔ)言,得益于它在原生態(tài)支持 協(xié)程并發(fā) 。這里我們首先了解進(jìn)程、線程和協(xié)程這三者的聯(lián)系和區(qū)別。

在多道程序系統(tǒng)中, 進(jìn)程 是一個(gè)具有獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次動(dòng)態(tài)執(zhí)行過(guò)程,是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是應(yīng)用程序運(yùn)行的載體。

而 線程 則是程序執(zhí)行過(guò)程中一個(gè)單一的順序控制流程,是 CPU 調(diào)度和分派的基本單位。 線程是比進(jìn)程更小的獨(dú)立運(yùn)行基本單位 ,一個(gè)進(jìn)程中可以擁有一個(gè)或者以上的線程,這些線程共享進(jìn)程所持有的資源,在 CPU 中被調(diào)度執(zhí)行,共同完成進(jìn)程的執(zhí)行任務(wù)。

在 Linux 系統(tǒng)中,根據(jù)資源訪問(wèn)權(quán)限的不同,操作系統(tǒng)會(huì)把內(nèi)存空間分為內(nèi)核空間和用戶(hù)空間:內(nèi)核空間的代碼能夠直接訪問(wèn)計(jì)算機(jī)的底層資源,如 CPU 資源、I/O 資源等,為用戶(hù)空間的代碼提供計(jì)算機(jī)底層資源訪問(wèn)能力;用戶(hù)空間為上層應(yīng)用程序的活動(dòng)空間,無(wú)法直接訪問(wèn)計(jì)算機(jī)底層資源,需要借助“系統(tǒng)調(diào)用”“庫(kù)函數(shù)”等方式調(diào)用內(nèi)核空間提供的資源。

同樣,線程也可以分為內(nèi)核線程和用戶(hù)線程。 內(nèi)核線程 由操作系統(tǒng)管理和調(diào)度,是內(nèi)核調(diào)度實(shí)體,它能夠直接操作計(jì)算機(jī)底層資源,可以充分利用 CPU 多核并行計(jì)算的優(yōu)勢(shì),但是線程切換時(shí)需要 CPU 切換到內(nèi)核態(tài),存在一定的開(kāi)銷(xiāo),可創(chuàng)建的線程數(shù)量也受到操作系統(tǒng)的限制。 用戶(hù)線程 由用戶(hù)空間的代碼創(chuàng)建、管理和調(diào)度,無(wú)法被操作系統(tǒng)感知。用戶(hù)線程的數(shù)據(jù)保存在用戶(hù)空間中,切換時(shí)無(wú)須切換到內(nèi)核態(tài),切換開(kāi)銷(xiāo)小且高效,可創(chuàng)建的線程數(shù)量理論上只與內(nèi)存大小相關(guān)。

協(xié)程是一種用戶(hù)線程,屬于輕量級(jí)線程。協(xié)程的調(diào)度,完全由用戶(hù)空間的代碼控制;協(xié)程擁有自己的寄存器上下文和棧,并存儲(chǔ)在用戶(hù)空間;協(xié)程切換時(shí)無(wú)須切換到內(nèi)核態(tài)訪問(wèn)內(nèi)核空間,切換速度極快。但這也給開(kāi)發(fā)人員帶來(lái)較大的技術(shù)挑戰(zhàn):開(kāi)發(fā)人員需要在用戶(hù)空間處理協(xié)程切換時(shí)上下文信息的保存和恢復(fù)、??臻g大小的管理等問(wèn)題。

Go 是為數(shù)不多在語(yǔ)言層次實(shí)現(xiàn)協(xié)程并發(fā)的語(yǔ)言,它采用了一種特殊的兩級(jí)線程模型:MPG 線程模型(如下圖)。

MPG 線程模型

  • M,即 machine,相當(dāng)于內(nèi)核線程在 Go 進(jìn)程中的映射,它與內(nèi)核線程一一對(duì)應(yīng),代表真正執(zhí)行計(jì)算的資源。在 M 的生命周期內(nèi),它只會(huì)與一個(gè)內(nèi)核線程關(guān)聯(lián)。
  • P,即 processor,代表 Go 代碼片段執(zhí)行所需的上下文環(huán)境。M 和 P 的結(jié)合能夠?yàn)?G 提供有效的運(yùn)行環(huán)境,它們之間的結(jié)合關(guān)系不是固定的。P 的最大數(shù)量決定了 Go 程序的并發(fā)規(guī)模,由 runtime.GOMAXPROCS 變量決定。
  • G,即 goroutine,是一種輕量級(jí)的用戶(hù)線程,是對(duì)代碼片段的封裝,擁有執(zhí)行時(shí)的棧、狀態(tài)和代碼片段等信息。

在實(shí)際執(zhí)行過(guò)程中,M 和 P 共同為 G 提供有效的運(yùn)行環(huán)境(如下圖),多個(gè)可執(zhí)行的 G 順序掛載在 P 的可執(zhí)行 G 隊(duì)列下面,等待調(diào)度和執(zhí)行。當(dāng) G 中存在一些 I/O 系統(tǒng)調(diào)用阻塞了 M時(shí),P 將會(huì)斷開(kāi)與 M 的聯(lián)系,從調(diào)度器空閑 M 隊(duì)列中獲取一個(gè) M 或者創(chuàng)建一個(gè)新的 M 組合執(zhí)行, 保證 P 中可執(zhí)行 G 隊(duì)列中其他 G 得到執(zhí)行,且由于程序中并行執(zhí)行的 M 數(shù)量沒(méi)變,保證了程序 CPU 的高利用率。

M 和 P 結(jié)合示意圖

當(dāng) G 中系統(tǒng)調(diào)用執(zhí)行結(jié)束返回時(shí),M 會(huì)為 G 捕獲一個(gè) P 上下文,如果捕獲失敗,就把 G 放到全局可執(zhí)行 G 隊(duì)列等待其他 P 的獲取。新創(chuàng)建的 G 會(huì)被放置到全局可執(zhí)行 G 隊(duì)列中,等待調(diào)度器分發(fā)到合適的 P 的可執(zhí)行 G 隊(duì)列中。M 和 P 結(jié)合后,會(huì)從 P 的可執(zhí)行 G 隊(duì)列中無(wú)鎖獲取 G 執(zhí)行。當(dāng) P 的可執(zhí)行 G 隊(duì)列為空時(shí),P 才會(huì)加鎖從全局可執(zhí)行 G 隊(duì)列獲取 G。當(dāng)全局可執(zhí)行 G 隊(duì)列中也沒(méi)有 G 時(shí),P 會(huì)嘗試從其他 P 的可執(zhí)行 G 隊(duì)列中“剽竊”G 執(zhí)行。

goroutine 和 channel

并發(fā)程序中的多個(gè)線程同時(shí)在 CPU 執(zhí)行,由于資源之間的相互依賴(lài)和競(jìng)態(tài)條件,需要一定的并發(fā)模型協(xié)作不同線程之間的任務(wù)執(zhí)行。Go 中倡導(dǎo)使用 CSP 并發(fā)模型 來(lái)控制線程之間的任務(wù)協(xié)作,CSP 倡導(dǎo)使用通信的方式來(lái)進(jìn)行線程之間的內(nèi)存共享。

Go是通過(guò) goroutine 和 channel 來(lái)實(shí)現(xiàn) CSP 并發(fā)模型的:

  • goroutine,即協(xié)程 ,Go 中的并發(fā)實(shí)體,是一種輕量級(jí)的用戶(hù)線程,是消息的發(fā)送和接收方;
  • channel,即通道 , goroutine 使用通道發(fā)送和接收消息。

CSP并發(fā)模型類(lèi)似常用的同步隊(duì)列,它更加關(guān)注消息的傳輸方式,解耦了發(fā)送消息的 goroutine 和接收消息的 goroutine,channel 可以獨(dú)立創(chuàng)建和存取,在不同的 goroutine 中傳遞使用。

使用關(guān)鍵字 go 即可使用 goroutine 并發(fā)執(zhí)行代碼片段,形式如下:

  1. go expression 

而 channel 作為一種引用類(lèi)型,聲明時(shí)需要指定傳輸數(shù)據(jù)類(lèi)型,聲明形式如下:

  1. var name chan T // 雙向 channel 
  2. var name chan <- T // 只能發(fā)送消息的 channel 
  3. var name T <- chan // 只能接收消息的 channel 

其中,T 即為 channel 可傳輸?shù)臄?shù)據(jù)類(lèi)型。channel 作為隊(duì)列,遵循消息先進(jìn)先出的順序,同時(shí)保證同一時(shí)刻只能有一個(gè) goroutine 發(fā)送或者接收消息。

使用 channel 發(fā)送和接收消息形式如下:

  1. channel <- val // 發(fā)送消息 
  2. val := <- channel // 接收消息 
  3. val, ok := <- channel // 非阻塞接收消息 

goroutine 向已經(jīng)填滿(mǎn)信息的 channel 發(fā)送信息或從沒(méi)有數(shù)據(jù)的 channel 接收信息會(huì)阻塞自身。goroutine 接收消息時(shí)可以使用非阻塞的方式,無(wú)論 channel 中是否存在消息都會(huì)立即返回,通過(guò) ok 布爾值判斷是否接收成功。

創(chuàng)建一個(gè) channel 需要使用 make 函數(shù)對(duì) channel 進(jìn)行初始化,形式如下所示:

  1. ch := make(chan T, sizeOfChan) 

初始化 channel 時(shí)可以指定 channel 的長(zhǎng)度,表示 channel 最多可以緩存多少條信息。下面我們通過(guò)一個(gè)簡(jiǎn)單例子演示 goroutine 和 channel 的使用:

  1. package main 
  2. import ( 
  3. "fmt" 
  4. "time" 
  5. //生產(chǎn)者 
  6. func Producer(begin, end int, queue chan<- int) { 
  7. for i:= begin ; i < end ; i++ { 
  8. fmt.Println("produce:", i) 
  9. queue <- i 
  10. //消費(fèi)者 
  11. func Consumer(queue <-chan int) { 
  12. for val := range queue  { //當(dāng)前的消費(fèi)者循環(huán)消費(fèi) 
  13. fmt.Println("consume:", val) 
  14. func main() { 
  15. queue := make(chan int
  16. defer close(queue) 
  17. for i := 0; i < 3; i++ { 
  18. go Producer(i * 5, (i+1) * 5, queue) //多個(gè)生產(chǎn)者 
  19. go Consumer(queue) //單個(gè)消費(fèi)者 
  20. time.Sleep(time.Second) // 避免主 goroutine 結(jié)束程序 

這是一個(gè)簡(jiǎn)單的多生產(chǎn)者和單消費(fèi)的代碼例子,生產(chǎn) goroutine 將生產(chǎn)的數(shù)字通過(guò) channel 發(fā)送給消費(fèi) goroutine。上述例子中,消費(fèi) goroutine 使用 for:range 從 channel 中循環(huán)接收消息,只有當(dāng)相應(yīng)的 channel 被內(nèi)置函數(shù) close 后,該循環(huán)才會(huì)結(jié)束。channel 在關(guān)閉之后不可以再用于發(fā)送消息,但是可以繼續(xù)用于接收消息,從關(guān)閉的 channel 中接收消息或者正在被阻塞的 goroutine 將會(huì)接收零值并返回。還有一個(gè)需要注意的點(diǎn)是,main 函數(shù)由主 goroutine 啟動(dòng),當(dāng)主 goroutine 即 main 函數(shù)執(zhí)行結(jié)束,整個(gè) Go 程序也會(huì)直接執(zhí)行結(jié)束,無(wú)論是否存在其他未執(zhí)行完的 goroutine。

1. select 多路復(fù)用

當(dāng)需要從多個(gè) channel 中接收消息時(shí),可以使用 Go 提供的 select 關(guān)鍵字,它提供類(lèi)似多路復(fù)用的能力,使得 goroutine 可以同時(shí)等待多個(gè) channel 的讀寫(xiě)操作。select 的形式與 switch 類(lèi)似,但是要求 case 語(yǔ)句后面必須為 channel 的收發(fā)操作,一個(gè)簡(jiǎn)單的例子如下:

  1. package main 
  2. import ( 
  3. "fmt" 
  4. "time" 
  5. func send(ch chan int, begin int )  { 
  6. // 循環(huán)向 channel 發(fā)送消息 
  7. for i :=begin ; i< begin + 10 ;i++{ 
  8. ch <- i 
  9. func receive(ch <-chan int)  { 
  10. val := <- ch 
  11. fmt.Println("receive:", val) 
  12. func main()  { 
  13. ch1 := make(chan int
  14. ch2 := make(chan int
  15. go send(ch1, 0
  16. go receive(ch2) 
  17. // 主 goroutine 休眠 1s,保證調(diào)度成功 
  18. time.Sleep(time.Second) 
  19. for { 
  20. select { 
  21. case val := <- ch1: // 從 ch1 讀取數(shù)據(jù) 
  22. fmt.Printf("get value %d from ch1\n", val) 
  23. case ch2 <- 2 : // 使用 ch2 發(fā)送消息 
  24. fmt.Println("send value by ch2"
  25. case <-time.After(2 * time.Second): // 超時(shí)設(shè)置 
  26. fmt.Println("Time out"
  27. return 

在上述例子中,我們使用 select 關(guān)鍵字同時(shí)從 ch1 中接收數(shù)據(jù)和使用 ch2 發(fā)送數(shù)據(jù),輸出的一種可能結(jié)果為:

  1. get value 0 from ch1 
  2. get value 1 from ch1 
  3. send value by ch2 
  4. receive: 2 
  5. get value 2 from ch1 
  6. get value 3 from ch1 
  7. get value 4 from ch1 
  8. get value 5 from ch1 
  9. get value 6 from ch1 
  10. get value 7 from ch1 
  11. get value 8 from ch1 
  12. get value 9 from ch1 
  13. Time out 

由于 ch2 中的消息僅被接收一次,所以?xún)H出現(xiàn)一次“send value by ch2”,后續(xù)消息的發(fā)送將被阻塞。select 語(yǔ)句分別從 3 個(gè) case 中選取返回的 case 進(jìn)行處理,當(dāng)有多個(gè) case 語(yǔ)句同時(shí)返回時(shí),select 將會(huì)隨機(jī)選擇一個(gè) case 進(jìn)行處理。如果 select 語(yǔ)句的最后包含 default 語(yǔ)句,該 select 語(yǔ)句將會(huì)變?yōu)榉亲枞?,即?dāng)其他所有的 case 語(yǔ)句都被阻塞無(wú)法返回時(shí),select 語(yǔ)句將直接執(zhí)行 default 語(yǔ)句返回結(jié)果。在上述例子中,我們?cè)谧詈蟮?case 語(yǔ)句使用了 <-time.After(2 * time.Second) 的方式指定了定時(shí)返回的 channel,這是一種有效從阻塞的 channel 中超時(shí)返回的小技巧。

2. Context 上下文

當(dāng)需要在多個(gè) goroutine 中傳遞上下文信息時(shí),可以使用 Context 實(shí)現(xiàn)。Context 除了用來(lái)傳遞上下文信息,還可以用于傳遞終結(jié)執(zhí)行子任務(wù)的相關(guān)信號(hào),中止多個(gè)執(zhí)行子任務(wù)的 goroutine。Context 中提供以下接口:

  1. type Context interface { 
  2. Deadline() (deadline time.Time, ok bool) 
  3. Done() <-chan struct{} 
  4. Err() error 
  5. Value(key interface{}) interface{} 
  • Deadline 方法,返回 Context 被取消的時(shí)間,也就是完成工作的截止日期;
  • Done,返回一個(gè) channel,這個(gè)channel 會(huì)在當(dāng)前工作完成或者上下文被取消之后關(guān)閉,多次調(diào)用 Done 方法會(huì)返回同一個(gè) channel;
  • Err 方法,返回 Context 結(jié)束的原因,它只會(huì)在 Done 返回的 channel 被關(guān)閉時(shí)才會(huì)返回非空的值,如果 Context 被取消,會(huì)返回 Canceled 錯(cuò)誤;如果 Context 超時(shí),會(huì)返回 DeadlineExceeded 錯(cuò)誤。
  • Value 方法,可用于從 Context 中獲取傳遞的鍵值信息。

在 Web 請(qǐng)求的處理過(guò)程中,一個(gè)請(qǐng)求可能啟動(dòng)多個(gè) goroutine 協(xié)同工作,這些 goroutine 之間可能需要共享請(qǐng)求的信息,且當(dāng)請(qǐng)求被取消或者執(zhí)行超時(shí)時(shí),該請(qǐng)求對(duì)應(yīng)的所有 goroutine 都需要快速結(jié)束,釋放資源。Context 就是為了解決上述場(chǎng)景而開(kāi)發(fā)的,我們通過(guò)下面一個(gè)例子來(lái)演示:

  1. package main 
  2. import ( 
  3. "context" 
  4. "fmt" 
  5. "time" 
  6. const DB_ADDRESS  = "db_address" 
  7. const CALCULATE_VALUE  = "calculate_value" 
  8. func readDB(ctx context.Context, cost time.Duration)  { 
  9. fmt.Println("db address is", ctx.Value(DB_ADDRESS)) 
  10. select { 
  11. case <- time.After(cost): //  模擬數(shù)據(jù)庫(kù)讀取 
  12. fmt.Println("read data from db"
  13. case <-ctx.Done(): 
  14. fmt.Println(ctx.Err()) // 任務(wù)取消的原因 
  15. // 一些清理工作 
  16. func calculate(ctx context.Context, cost time.Duration)  { 
  17. fmt.Println("calculate value is", ctx.Value(CALCULATE_VALUE)) 
  18. select { 
  19. case <- time.After(cost): //  模擬數(shù)據(jù)計(jì)算 
  20. fmt.Println("calculate finish"
  21. case <-ctx.Done(): 
  22. fmt.Println(ctx.Err()) // 任務(wù)取消的原因 
  23. // 一些清理工作 
  24. func main()  { 
  25. ctx := context.Background(); // 創(chuàng)建一個(gè)空的上下文 
  26. // 添加上下文信息 
  27. ctx = context.WithValue(ctx, DB_ADDRESS, "localhost:10086"
  28. ctx = context.WithValue(ctx, CALCULATE_VALUE, 1234
  29. // 設(shè)定子 Context 2s 后執(zhí)行超時(shí)返回 
  30. ctx, cancel := context.WithTimeout(ctx, time.Second * 2
  31. defer cancel() 
  32. // 設(shè)定執(zhí)行時(shí)間為 4 s 
  33. go readDB(ctx, time.Second * 4
  34. go calculate(ctx, time.Second * 4
  35.  
  36. // 充分執(zhí)行 
  37. time.Sleep(time.Second * 5

在上述例子中,我們模擬了一個(gè)請(qǐng)求中同時(shí)進(jìn)行數(shù)據(jù)庫(kù)訪問(wèn)和邏輯計(jì)算的操作,在請(qǐng)求執(zhí)行超時(shí)時(shí),及時(shí)關(guān)閉尚未執(zhí)行結(jié)束 goroutine。我們首先通過(guò) context.WithValue 方法為 context 添加上下文信息,Context 在多個(gè) goroutine 中是并發(fā)安全的,可以安全地在多個(gè) goroutine 中對(duì) Context 中的上下文數(shù)據(jù)進(jìn)行讀取。接著使用 context.WithTimeout 方法設(shè)定了 Context 的超時(shí)時(shí)間為 2s,并傳遞給 readDB 和 calculate 兩個(gè) goroutine 執(zhí)行子任務(wù)。在 readDB 和 calculate 方法中,使用 select 語(yǔ)句對(duì) Context 的 Done 通道進(jìn)行監(jiān)控。由于我們?cè)O(shè)定了子 Context 將在 2s 之后超時(shí),所以它將在 2s 之后關(guān)閉 Done 通道;然而預(yù)設(shè)的子任務(wù)執(zhí)行時(shí)間為 4s,對(duì)應(yīng)的 case 語(yǔ)句尚未返回,執(zhí)行被取消,進(jìn)入到清理工作的 case 語(yǔ)句中,結(jié)束掉當(dāng)前的 goroutine 所執(zhí)行的任務(wù)。預(yù)期的輸出結(jié)果如下:

  1. calculate value is 1234 
  2. db address is localhost:10086 
  3. context deadline exceeded 
  4. context deadline exceeded 

使用 Context,能夠有效地在一組 goroutine 中傳遞共享值、取消信號(hào)、deadline 等信息,及時(shí)關(guān)閉不需要的 goroutine。

小結(jié)

本文我們主要介紹了 Go 語(yǔ)言并發(fā)特性,主要包含:

  • Go 的 MPG 線程模型;
  • goroutine 和 channel;
  • select 多路復(fù)用;
  • Context 上下文。

除了支持 CSP 的并發(fā)模型,Go 同樣支持傳統(tǒng)的線程與鎖并發(fā)模型,提供了互斥鎖、讀寫(xiě)鎖、并發(fā)等待組、同步等待條件等一系列同步工具,這些同步工具的結(jié)構(gòu)體位于 sync 包中,與其他語(yǔ)言的同步工具使用方式相差無(wú)幾。Go 在語(yǔ)言層次支持協(xié)程并發(fā),在并發(fā)性能上表現(xiàn)卓越,能夠充分挖掘多核 CPU 的運(yùn)算性能。希望本文的學(xué)習(xí),能夠有效提升你對(duì) Go 并發(fā)設(shè)計(jì)和編程的認(rèn)知。

責(zé)任編輯:張燕妮 來(lái)源: Aoho's Blog
相關(guān)推薦

2018-10-11 09:40:53

前端JavaScript編程語(yǔ)言

2020-07-08 08:22:08

FlutterSVGPNG

2019-03-22 14:20:26

管理多云云計(jì)算

2015-02-26 09:19:00

2022-09-12 23:53:53

JavaScript條件判斷開(kāi)發(fā)

2012-02-08 16:19:09

ibmdw

2012-02-14 12:50:13

ibmdw

2022-06-02 08:28:25

Docker代碼運(yùn)維

2012-07-24 09:30:25

企業(yè)應(yīng)用程序SAP

2015-11-18 09:56:24

數(shù)據(jù)中心監(jiān)控

2017-03-31 22:45:11

iOSiOS 10.3開(kāi)發(fā)者

2022-05-06 15:58:44

物聯(lián)網(wǎng)社區(qū)物聯(lián)網(wǎng)應(yīng)用

2021-01-28 14:53:19

PHP編碼開(kāi)發(fā)

2024-08-05 09:36:03

2022-05-05 16:49:12

物聯(lián)網(wǎng)社區(qū)應(yīng)急

2017-10-12 15:20:57

數(shù)據(jù)中心遷移數(shù)據(jù)云端

2014-04-09 09:32:24

Go并發(fā)

2023-11-09 11:48:41

2012-12-20 09:46:42

應(yīng)用虛擬化

2021-05-12 22:07:43

并發(fā)編排任務(wù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)