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

「Go語言進(jìn)階」并發(fā)編程詳解

開發(fā) 前端
在很多情況下,并發(fā)的效果比并行好,因?yàn)椴僮飨到y(tǒng)和硬件的總資源一般很少,但能支持系統(tǒng)同時做很多事情。這種“使用較少的資源做更多的事情”的哲學(xué),也是指導(dǎo) Go語言設(shè)計的哲學(xué)。

并發(fā) VS 并行

在講解并發(fā)概念時,總會涉及另外一個概念并行。下面讓我們來聊聊并發(fā)和并行之間的區(qū)別。

  • 并發(fā)(concurrency):把任務(wù)在不同的時間點(diǎn)交給處理器進(jìn)行處理。在同一時間點(diǎn),任務(wù)并不會同時運(yùn)行。
  • 并行(parallelism):把每一個任務(wù)分配給每一個處理器獨(dú)立完成。在同一時間點(diǎn),任務(wù)一定是同時運(yùn)行。

并發(fā)不是并行。并行是讓不同的代碼片段同時在不同的物理處理器上執(zhí)行。并行的關(guān)鍵是同時做很多事情,而并發(fā)是指同時管理很多事情,這些事情可能只做了一半就被暫停去做別的事情了。

在很多情況下,并發(fā)的效果比并行好,因?yàn)椴僮飨到y(tǒng)和硬件的總資源一般很少,但能支持系統(tǒng)同時做很多事情。這種“使用較少的資源做更多的事情”的哲學(xué),也是指導(dǎo) Go語言設(shè)計的哲學(xué)。

如果希望讓 goroutine 并行,必須使用多于一個邏輯處理器。當(dāng)有多個邏輯處理器(CPU)時,調(diào)度器會將 goroutine 平等分配到每個邏輯處理器上。這會讓 goroutine 在不同的線程上運(yùn)行。不過要想真的實(shí)現(xiàn)并行的效果,用戶需要讓自己的程序運(yùn)行在有多個物理處理器的機(jī)器上。否則,哪怕 Go語言運(yùn)行時使用多個線程,goroutine 依然會在同一個物理處理器上并發(fā)運(yùn)行,達(dá)不到并行的效果。

下圖展示了在一個邏輯處理器上并發(fā)運(yùn)行 goroutine 和在兩個邏輯處理器上并行運(yùn)行兩個并發(fā)的 goroutine 之間的區(qū)別。 調(diào)度器包含一些聰明的算法,這些算法會隨著Go語言的發(fā)布被更新和改進(jìn),所以不推薦盲目修改語言運(yùn)行時對邏輯處理器的默認(rèn)設(shè)置。如果真的認(rèn)為修改邏輯處理器的數(shù)量可以改進(jìn)性能,也可以對語言運(yùn)行時的參數(shù)進(jìn)行細(xì)微調(diào)整。

圖片

并發(fā)與并行的區(qū)別

Go 可以充分發(fā)揮多核優(yōu)勢,高效運(yùn)行。 Go語言在 GOMAXPROCS 數(shù)量與任務(wù)數(shù)量相等時,可以做到并行執(zhí)行,但一般情況下都是并發(fā)執(zhí)行。

目錄

  • 1.1 Goroutine
  • 1.2 CSP
  • 1.3 Channel
  • 1.4 Lock
  • 1.5 WaitGroup

1.1 Goroutine

由誰創(chuàng)建?

  • 線程是操作系統(tǒng)分配給應(yīng)用程序的獨(dú)立執(zhí)行單元,它們可以在多核處理器中并行執(zhí)行。線程的調(diào)度是由操作系統(tǒng)內(nèi)核負(fù)責(zé)的,并且線程之間有獨(dú)立的地址空間。
  • 協(xié)程是由程序員編寫的,它是一種輕量級的線程,并由Go語言運(yùn)行時管理。協(xié)程之間沒有獨(dú)立的地址空間,而是共享一個地址空間。協(xié)程的調(diào)度是由Go語言運(yùn)行時負(fù)責(zé)的,并且可以在單個線程中并行執(zhí)行。

線程的創(chuàng)建和銷毀的開銷比較大,而協(xié)程的創(chuàng)建和銷毀開銷很小,因此在需要高并發(fā)的場景中,使用協(xié)程更加高效。

大小比較?

線程棧是由操作系統(tǒng)分配的,它通常有一個固定的大小,并且在線程創(chuàng)建時分配。它存儲著線程的狀態(tài)信息和調(diào)用棧。線程棧的大小取決于操作系統(tǒng)的限制,一般在幾百KB到幾MB之間。

而協(xié)程的棧是由Go語言運(yùn)行時管理的,它通常有一個較小的默認(rèn)大小,并在協(xié)程創(chuàng)建時分配。它也存儲著協(xié)程的狀態(tài)信息和調(diào)用棧。協(xié)程棧的大小可以通過Golang的runtime包中的函數(shù)來調(diào)整,一般在幾KB到幾MB之間。

由于協(xié)程的棧比線程棧小,所以協(xié)程能夠創(chuàng)建的數(shù)量比線程多得多。但是由于協(xié)程棧比線程棧小,所以在調(diào)用深度較深的程序中,協(xié)程可能會爆棧。

1.2 CSP

CSP:Communicating Sequential Processes

Go語言提倡:通過通信共享內(nèi)存,而不是通過共享內(nèi)存而實(shí)現(xiàn)通信。

有緩沖通道

緩沖通道中的數(shù)字表示該通道可以在沒有接收者阻塞的情況下緩存多少個元素。

加入容量為1,所以只能緩存一個元素。如果一個新的元素試圖被發(fā)送到已經(jīng)滿了的通道中,發(fā)送者將會阻塞直到接收者從通道中讀取一個元素。

阻塞并不一定意味著數(shù)據(jù)丟失,這取決于阻塞的原因和應(yīng)用程序的設(shè)計:

在 Go 語言中,通道是一種同步機(jī)制,發(fā)送者和接收者之間可以通過通道來進(jìn)行通信。 如果發(fā)送者試圖向一個滿的緩沖通道發(fā)送數(shù)據(jù),那么發(fā)送者將會阻塞直到緩沖區(qū)有空間可用。同樣,如果接收者試圖從一個空的通道接收數(shù)據(jù),那么接收者將會阻塞直到通道中有數(shù)據(jù)可用。這種情況下,數(shù)據(jù)不會丟失,而是在緩沖區(qū)中等待被取出。

無緩沖通道

但是,如果通道是無緩沖的,那么發(fā)送者和接收者之間將是同步的。如果發(fā)送者在接收者準(zhǔn)備好之前發(fā)送了數(shù)據(jù),那么發(fā)送者將會阻塞直到接收者準(zhǔn)備好。

如果接收者在數(shù)據(jù)可用之前就開始接收,那么接收者將會阻塞直到數(shù)據(jù)可用。在這種情況下,如果發(fā)送者和接收者之間的時間差較大,那么可能會導(dǎo)致數(shù)據(jù)丟失。

所以阻塞并不一定意味著數(shù)據(jù)丟失,而是取決于程序是否設(shè)計了阻塞的處理方式,以及阻塞的類型。

下面是一個示例代碼,其中兩個 goroutine 通過緩沖通道共享內(nèi)存:

package main

import (
"fmt"
)

func main() {
// 創(chuàng)建緩沖通道
ch := make(chan int, 1)

// 啟動第一個goroutine
go func() {
for i := 0; i < 10; i++ {
ch <- i // 發(fā)送數(shù)據(jù)
}
close(ch) // 關(guān)閉通道
}()

// 啟動第二個goroutine
go func() {
for i := range ch {
fmt.Println(i) // 接收數(shù)據(jù)并打印
}
}()

// 等待所有g(shù)oroutine結(jié)束
fmt.Scanln()
}

執(zhí)行效果:

圖片

在這個示例中,第一個 goroutine 會循環(huán)發(fā)送 0 到 9 的整數(shù),而第二個 goroutine 會接收這些整數(shù)并打印。這兩個 goroutine 都會共享同一個通道來傳遞數(shù)據(jù)。

注意,在生產(chǎn)環(huán)境中,通常需要使用同步機(jī)制來等待 goroutine 結(jié)束,而不是使用 fmt.Scanln()。

1.3 Channel

make(chan 元素類型,[緩沖大小])

  • 無緩沖通道 make(chan int) 同步
  • 有緩沖通道 make(chan int,2) 不同步

無緩沖通道是在發(fā)送者和接收者之間同步地傳遞消息。 發(fā)送者會在接收者準(zhǔn)備好接收消息之前阻塞,接收者會在接收到消息之前阻塞。這種方式可以保證消息的順序和每個消息只被接收一次。

緩沖通道具有一個固定大小的緩沖區(qū),發(fā)送者和接收者之間不再是同步的。 如果緩沖區(qū)已滿,發(fā)送者會繼續(xù)執(zhí)行而不會阻塞;如果緩沖區(qū)為空,接收者會繼續(xù)執(zhí)行而不會阻塞。這種方式可以提高程序的性能,但是可能會導(dǎo)致消息的丟失或重復(fù)。

package main

import (
"fmt"
)

func main() {
// 創(chuàng)建通道
ch := make(chan int)
ch_squared := make(chan int)

// 啟動A子協(xié)程
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}()

// 啟動B子協(xié)程
go func() {
for i := range ch {
ch_squared <- i * i
}
close(ch_squared)
}()

//主協(xié)程輸出結(jié)果
for i := range ch_squared {
fmt.Println(i)
}
}

執(zhí)行效果:

圖片

在這個程序中,A子協(xié)程循環(huán)發(fā)送0~9的數(shù)字,B子協(xié)程接收并計算數(shù)字的平方,最后主協(xié)程等待所有子協(xié)程完成后輸出所有數(shù)字的平方。

注意:

  • 在這個程序中我們使用了兩個通道ch, ch_squared來傳遞數(shù)據(jù),以避免數(shù)據(jù)丟失。
  • 在最后輸出結(jié)果時,主協(xié)程要等待所有子協(xié)程完成,因此我們使用了 for i := range ch_squared來等待子協(xié)程的完成
  • 在生產(chǎn)環(huán)境中,通常需要使用同步機(jī)制來等待子協(xié)程結(jié)束,而不是使用 for i := range ch_squared。
  • 可以把ch_squared改為帶緩沖的channe,以解決生產(chǎn)比消費(fèi)快的執(zhí)行效率問題。

1.4 并發(fā)安全 Lock

在并發(fā)編程中,當(dāng)多個 goroutine 同時訪問共享資源時,可能會出現(xiàn)競爭條件,導(dǎo)致數(shù)據(jù)不一致或錯誤。為了避免這種情況,我們可以使用 Lock(鎖)來保證并發(fā)安全。

Lock 是一種同步機(jī)制,可以防止多個 goroutine 同時訪問共享資源。當(dāng)一個 goroutine 獲取鎖時,其他 goroutine 將被阻塞,直到鎖被釋放。

Go語言標(biāo)準(zhǔn)庫中提供了 sync.Mutex 來實(shí)現(xiàn)鎖。

一個簡單的例子:

package main

import (
"fmt"
"sync"
)

var (
count int
lock sync.Mutex
)

func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
lock.Lock()
defer lock.Unlock()
count++
fmt.Println(count)
}()
}
wg.Wait()
}

執(zhí)行效果:

圖片

在上面的示例中,main函數(shù)中啟動了10個goroutine,每個goroutine都會嘗試去獲取鎖,并對共享變量count進(jìn)行修改。在獲取鎖后才能進(jìn)行修改,其他goroutine在等待鎖時將被阻塞。

這樣就能保證并發(fā)安全了,使得共享變量count在多個goroutine之間可以安全地訪問。但是,使用鎖也需要注意避免死鎖的情況,需要在適當(dāng)?shù)臅r候釋放鎖。并發(fā)安全問題難以定位。

1.5 WaitGroup

Go語言標(biāo)準(zhǔn)庫中提供了 sync.WaitGroup 來管理多個 goroutine 的執(zhí)行。

  • Add(delta int): 使用該方法來增加等待組中 goroutine 的數(shù)量。當(dāng)我們需要等待一些 goroutine 執(zhí)行完畢時,就可以使用該方法來增加等待組中 goroutine 的數(shù)量。
  • Done(): 使用該方法來通知等待組,一個 goroutine 執(zhí)行完畢。當(dāng)一個 goroutine 執(zhí)行完畢后,我們需要調(diào)用該方法來通知等待組。
  • Wait(): 使用該方法來等待等待組中的所有 goroutine 執(zhí)行完畢。當(dāng)我們需要等待所有 goroutine 執(zhí)行完畢時,就可以使用該方法。

下面是一個例子,演示了如何使用 sync.WaitGroup 來管理多個 goroutine 的執(zhí)行:

package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
wg.Add(3) //增加3個goroutine

go func() {
defer wg.Done()
fmt.Println("Goroutine 1")
}()
go func() {
defer wg.Done()
fmt.Println("Goroutine 2")
}()
go func() {
defer wg.Done()
fmt.Println("Goroutine 3")
}()

wg.Wait()
fmt.Println("all goroutines have been finished")
}

執(zhí)行效果:

圖片

在上面的代碼中,我們使用了 sync.WaitGroup 來管理三個 goroutine 的執(zhí)行。我們先使用 wg.Add(3) 來增加等待組中 goroutine 的數(shù)量。然后在每個 goroutine 中調(diào)用 wg.Done() 來通知等待組,該 goroutine 執(zhí)行完畢。最后使用 wg.Wait() 來等待所有 goroutine 執(zhí)行完畢。

注意:

  • 如果沒有 wg.Wait(),主協(xié)程可能會在其他協(xié)程還沒有執(zhí)行完成的情況下結(jié)束,這樣的話其他協(xié)程的執(zhí)行結(jié)果就沒有機(jī)會被獲取。
  • 如果Add的數(shù)量和done的數(shù)量不對應(yīng),wait永遠(yuǎn)不會返回,這也叫死鎖。

在線運(yùn)行

圖片

上面分享的代碼都支持,訪問下方鏈接運(yùn)行測試:https://1024code.com/codecubes/GB47x7u

本文轉(zhuǎn)載自微信公眾號「 程序員升級打怪之旅」,作者「王中陽Go」,可以通過以下二維碼關(guān)注。

轉(zhuǎn)載本文請聯(lián)系「 程序員升級打怪之旅」公眾號。

責(zé)任編輯:武曉燕 來源: 程序員升職加薪之旅
相關(guān)推薦

2024-09-06 10:48:13

2022-10-17 08:07:13

Go 語言并發(fā)編程

2021-09-30 09:21:28

Go語言并發(fā)編程

2025-03-24 00:25:00

Go語言并發(fā)編程

2023-07-14 08:12:21

計時器unsafecontext

2024-10-14 08:51:52

協(xié)程Go語言

2023-11-27 18:07:05

Go并發(fā)編程

2024-07-08 00:01:00

GPM模型調(diào)度器

2021-12-12 18:15:06

Python并發(fā)編程

2021-07-30 07:28:15

WorkerPoolGo語言

2023-12-21 07:09:32

Go語言任務(wù)

2013-05-28 09:43:38

GoGo語言并發(fā)模式

2021-07-15 23:18:48

Go語言并發(fā)

2024-07-30 12:24:23

2021-08-04 08:56:34

語言Go排序

2023-11-06 13:32:38

Go編程

2017-11-10 11:27:48

Go并行算法

2022-04-24 15:29:17

微服務(wù)go

2022-04-06 08:19:13

Go語言切片

2023-01-30 15:41:10

Channel控制并發(fā)
點(diǎn)贊
收藏

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