欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

GO語言中通道和sync包的使用教程分享

 更新時間:2023年02月24日 11:12:28   作者:阿兵云原生  
這篇文章主要為大家詳細介紹了Go語言中通道和sync包的相關資料,文中的示例代碼講解詳細,對我們學習Go語言有一定的幫助,需要的可以參考一下

GO通道和 sync 包的分享

我們一起回顧一下上次分享的內容:

  • GO協(xié)程同步若不做限制的話,會產生數據競態(tài)的問題
  • 我們用鎖的方式來解決如上問題,根據使用場景選擇使用互斥鎖 和 讀寫鎖
  • 比使用鎖更好的方式是原子操作,但是使用go的 sync/atomic需要小心使用,因為涉及內存

要是對GO的鎖和原子操作還感興趣的話,歡迎查看文章GO的鎖和原子操作分享

上次我們分享到鎖和原子操作,都可以保證共享數據的讀寫

可是,他們還是會影響性能,不過,Go 為開發(fā)這提供了 通道 這個神器

今天我們來分享一下Go中推薦使用的其他同步方法,通道和 sync 包

通道是什么

是一種特殊的類型,是連接并發(fā)goroutine的管道

channel 通道是可以讓一個 goroutine 協(xié)程發(fā)送特定值到另一個 goroutine 協(xié)程的通信機制。

通道像一個傳送帶或者隊列,總是遵循先入先出(First In First Out)的規(guī)則,保證收發(fā)數據的順序,這一點和管道是一樣的

一個協(xié)程從通道的一頭放入數據,另一個協(xié)程從通道的另一頭讀出數據

每一個通道都是一個具體類型的導管,聲明 channel 的時候需要為其指定元素類型。

通道能做什么

控制協(xié)程的同步,讓程序有序運行

GO 中提倡 不要通過共享內存來通信,而通過通信來共享內存

goroutine協(xié)程 是 Go 程序并發(fā)的執(zhí)行體,channel 通道就是它們之間的連接,他們之間的橋梁,他們的交通樞紐

通道有哪幾種

大致可分為如下三種:

  • 無緩沖通道
  • 有緩沖的通道
  • 單向通道

無緩沖通道

無緩沖的通道又稱為阻塞的通道

無緩沖通道上的發(fā)送操作會阻塞,直到另一個goroutine在該通道上執(zhí)行接收操作,這時值才能發(fā)送成功

兩個 goroutine 協(xié)程將繼續(xù)執(zhí)行

我們反過來看,如果接收操作先執(zhí)行,接收方的goroutine將阻塞,直到另一個 goroutine 協(xié)程在該通道上發(fā)送一個數據

因此,無緩沖通道也被稱為同步通道,因為我們可以使用無緩沖通道進行通信,利用發(fā)送和接收的 goroutine 協(xié)程同步化

有緩沖的通道

還是上述提到的,有緩沖通道,就是在初始化 / 創(chuàng)建通道 的 make 函數的第 2 個參數填上我們所期望的緩沖區(qū)大小 , 例如:

ch1 := make(chan int , 4)

此時,該通道的容量為4,發(fā)送方可以一直向通道中發(fā)送數據,直到通道滿,且通道數據未被讀走時,發(fā)送方就會阻塞

只要通道的容量大于零,那么該通道就是有緩沖的通道

通道的容量表示通道中能存放元素的數量

我們可以使用內置的 len函數 獲取通道內元素的數量,使用 cap函數 獲取通道的容量

單向通道

通道默認是既可以讀有可以寫的,但是單向通道就是要么只能讀,要么只能寫

1.chan <- int

是一個只能發(fā)送的通道,可以發(fā)送但是不能接收

2.<- chan int

是一個只能接收的通道,可以接收但是不能發(fā)送

如何創(chuàng)建和聲明一個通道

聲明通道

在 Go 里面,channel是一種類型,默認就是一種引用類型

簡單解釋一下什么是引用:

  • 在我們寫C++的時候,用到引用會比較多
  • 引用,顧名思義是某一個變量或對象的別名,對引用的操作與對其所綁定的變量或對象的操作完全等價
  • 在C++里面是這樣用的:
  • 類型 &引用名=目標變量名;

聲明一個通道

var 變量名 chan 元素類型

var ch1 chan string   			// 聲明一個傳遞字符串數據的通道
var ch2 chan []int 				// 聲明一個傳遞int切片數據的通道
var ch3 chan bool  				// 聲明一個傳遞布爾型數據的通道
var ch4 chan interface{}  		// 聲明一個傳遞接口類型數據的通道

看,聲明一個通道就是這么簡單

對于通道來說,關聲明了還不能使用,聲明的通道默認是其對應類型的零值,例如

  • int 類型 零值 就是 0
  • string 類型 零值就是個 空串
  • bool 類型 零值就是 false
  • 切片的 零值 就是 nil

我們還需要對通道進行初始化才可以正常使用通道哦

初始化通道

一般是使用 make 函數初始化之后才能使用通道,也可以直接使用make函數 創(chuàng)建通道

例如:

ch5 := make(chan string)
ch6 := make(chan []int)
ch7 := make(chan bool)
ch8 := make(chan interface{})

make 函數的第二個參數是可以設置緩沖的大小的,我們來看看源碼的說明

// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
// Slice: The size specifies the length. The capacity of the slice is
// equal to its length. A second integer argument may be provided to
// specify a different capacity; it must be no smaller than the
// length. For example, make([]int, 0, 10) allocates an underlying array
// of size 10 and returns a slice of length 0 and capacity 10 that is
// backed by this underlying array.
// Map: An empty map is allocated with enough space to hold the
// specified number of elements. The size may be omitted, in which case
// a small starting size is allocated.
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type

如果 make 函數的第二個參數不填,那么就默認是無緩沖的通道

現在我們來看看如何操作 channel 通道,都可以怎么玩

如何操作 channel

通道的操作有如下三種操作:

  • 發(fā)送(send)
  • 接收(receive)
  • 關閉(close)

對于發(fā)送和接收通道里面的數據,寫法就比較形象,使用 <- 來指向是從通道里面讀取數據,還是從通道中發(fā)送數據

向通道發(fā)送數據

// 創(chuàng)建一個通道
ch := make(chan int)
// 發(fā)送數據給通道
ch <- 1

我們看到箭頭的方向是,1 指向了 ch 通道,所以不難理解,這是將1 這個數據,放入通道中

從通道中接收數據

num := <-ch

不難看出,上述代碼是 ch 指向了一個需要初始化的變量,也就是說,從 ch 中讀出一個數據,賦值給 num

我們從通道中讀出數據,也可以不進行賦值,直接忽略也是可以的,如:

<-ch

關閉通道

Go中提供了 close 函數來關閉通道

close(ch)

對于關閉通道非常需要注意,用不好直接導致程序崩潰

  • 只有在通知接收方 goroutine 協(xié)程所有的數據都發(fā)送完畢的時候才需要關閉通道
  • 通道是可以被垃圾回收機制回收的,它和關閉文件是不一樣的,在結束操作之后關閉文件是必須要做的,但關閉通道不是必須的

關閉后的通道有以下 4 個特點:

  • 對一個關閉的通道再發(fā)送值就會導致 panic
  • 對一個關閉的通道進行接收會一直獲取值直到通道為空
  • 對一個關閉的并且沒有值的通道執(zhí)行接收操作會得到對應類型的零值
  • 關閉一個已經關閉的通道會導致 panic

通道異常情況梳理

我們來整理一下對于通道會存在的異常:

channel 狀態(tài)未初始化的通道(nil)通道非空通道是空的通道滿了通道未滿
接收數據阻塞接收數據阻塞接收數據接收數據
發(fā)送數據阻塞發(fā)送數據發(fā)送數據阻塞發(fā)送數據
關閉panic關閉通道成功
待數據讀取完畢后
返回零值
關閉通道成功
直接返回零值
關閉通道成功
待數據讀取完畢后
返回零值
關閉通道成功
待數據讀取完畢后
返回零值

每一種通道的DEMO實戰(zhàn)

無緩沖通道

func main() {
   // 創(chuàng)建一個無緩沖的,數據類型 為 int 類型的通道
   ch := make(chan int)
   // 向通道中寫入 數字 1
   ch <- 1
   fmt.Println("send successfully ... ")
}

執(zhí)行上述代碼我們可以查看到效果

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        F:/my_channel/main.go:9 +0x45
exit status 2

出現上述報錯 deadlock 錯誤的原因,細心的小伙伴應該能夠知道為什么,我上述有提到

我們使用 ch := make(chan int) 創(chuàng)建的是無緩沖的通道

無緩沖的通道只有在有接收方接收值的時候才能發(fā)送數據成功

我們可以想一下我們生活中的案例一樣:

你在某東上買了一個稍微貴重一點的物品,某東快遞人員給你寄快遞的時候,打電話給你,必須要送到你的手上,不然不敢簽收,這個時候,你不方便,或者你不簽收,那么這個快遞就是算作沒有寄送成功

因此,上述問題原因是,創(chuàng)建了一個無緩沖通道,發(fā)送方一直在阻塞,通道中一直未有協(xié)程讀取數據,導致死鎖

我們的解決辦法就是創(chuàng)建另外一個協(xié)程,將數據從通道中讀出來即可

package main

import "fmt"

func recvData(c chan int) {
	ret := <-c
	fmt.Println("recvData successfully ... data = ", ret)
}

func main() {
	// 創(chuàng)建一個無緩沖的,數據類型 為 int 類型的通道
	ch := make(chan int)
	go recvData(ch)
	// 向通道中寫入 數字 1
	ch <- 1
	fmt.Println("send successfully ... ")
}

這里需要注意,如果 go recvData(ch) 放在了 ch <- 1 之后,那么結果還是一樣的死鎖,原因還是因為 ch <- 1 會一直阻塞,根本不會執(zhí)行到 他之后的語句

實際效果

recvData successfully ... data =  1
send successfully ...

有緩沖通道

func main() {
   // 創(chuàng)建一個無緩沖的,數據類型 為 int 類型的通道
   ch := make(chan int , 1)
   // 向通道中寫入 數字 1
   ch <- 1
   fmt.Println("send successfully ... ")
}

還是同樣的案例,同樣的代碼,我們只是把無緩沖通道,換成了有緩沖的通道, 我們仍然不專門開協(xié)程讀取通道的數據

實際效果 , 發(fā)送成功

send successfully ...

因為此時通道中的緩沖是1,第一次向通道中發(fā)送數據,不會阻塞,

可是如果,在通道中數據還未讀取出去之前,又向通道中寫入數據,則此處會阻塞,

若一直沒有協(xié)程從通道中讀取數據,則結果與上述一樣,會死鎖

單向通道

package main

import "fmt"

func OnlyWriteData(out chan<- int) {
   // 單向 通道 , 只寫 不能讀
   for i := 0; i < 10; i++ {
      out <- i
   }
   close(out)
}

func CalData(out chan<- int, in <-chan int) {
   // out 單向 通道 , 只寫 不能讀
   // int 單向 通道 , 只讀 不能寫

   // 遍歷 讀取in 通道,若 in通道 數據讀取完畢,則阻塞,若in 通道關閉,則退出循環(huán)
   for i := range in {
      out <- i + i
   }
   close(out)
}
func myPrinter(in <-chan int) {
   // 遍歷 讀取in 通道,若 in通道 數據讀取完畢,則阻塞,若in 通道關閉,則退出循環(huán)
   for i := range in {
      fmt.Println(i)
   }
}

func main() {
   // 創(chuàng)建2 個無緩沖的通道
   ch1 := make(chan int)
   ch2 := make(chan int)


   go OnlyWriteData(ch1)
   go CalData(ch2, ch1)


   myPrinter(ch2)
}

我們模擬 2 個通道,

  • 一個 只寫 不能讀
  • 一個 只讀 不能寫

實際效果

0
2
4
6
8
10
12
14
16
18

關閉通道

package main

import "fmt"

func main() {
   c := make(chan int)
   
   go func() {
      for i := 0; i < 10; i++ {
         // 循環(huán)向無緩沖的通道中寫入數據, 只有當上一個數據被讀走之后,下一個數據才能往通道中放
         c <- i
      }
      // 關閉通道
      close(c)
   }()
   for {
      // 讀取通道中的數據,若通道中無數據,則阻塞,若讀到 ok 為false, 則通道關閉,退出循環(huán)
      if data, ok := <-c; ok {
         fmt.Println(data)
      } else {
         break
      }
   }
   fmt.Println("channel over")
}

再次強調一下關閉通道,demo 的模擬方式與上述的案例基本一致,感興趣的可以自己運行看看效果

看到這里,細心的小伙伴應該可以總結出,判斷通道是否關閉的 2種 方式了吧?

讀取通道的時候,判斷bool類型的變量是否為false

例如上述代碼

if data, ok := <-c; ok {
	fmt.Println(data)
} else {
	break
}

判斷 ok 為true,則正常讀取到數據, 若為false ,則通道關閉

通過 for range 的方式來遍歷通道,若退出循環(huán),則是因為通道關閉

sync 包

Go 的 sync 包也是用作實現并發(fā)任務的同步

還記得嗎,在分享 文章GO的鎖和原子操作分享的時候,我們就用到過 sync 包

用法大同消息,這里列舉一下 sync 包涉及的數據結構和方法

  • sync.WaitGroup
  • sync.Once
  • sync.Map

sync.WaitGroup

他是一個結構體,傳遞的時候要傳遞指針 ,這里需要注意

他是并發(fā)安全的,內部有維護一個計數器

涉及的方法:

(wg * WaitGroup) Add(delta int)

參數中 傳入的 delta ,表示 sync.WaitGroup 內部的計數器 + delta

(wg *WaitGroup) Done()

表示當前協(xié)程退出,計數器 -1

(wg *WaitGroup) Wait()

等待并發(fā)任務執(zhí)行完畢,此時的計數器為變成 0

sync.Once

他是并發(fā)安全的,內部有互斥鎖 和 一個布爾類型的數據

  • 互斥鎖 用于加鎖解鎖
  • 布爾類型的數據 用于記錄初始化是否完成

一般用于在高并發(fā)的場景下只執(zhí)行一次,我們一下子就能想到的場景會有程序啟動時,加載配置文件的場景

針對類似的場景,Go 也給我們提供了解決方法 ,即 sync.Once 里面的 Do 方法

func (o *Once) Do(f func()) {}

Do 方法的參數 是一個函數,可是我們要在該函數里面?zhèn)鬟f參數咋整?

可以使用Go 里面的閉包來實現 , 閉包的具體實現方式,感興趣的可以深入了解一下

sync.Map

他是并發(fā)安全的,正是因為 Go 中的 map 是并發(fā)不安全的,因此有了 sync.Map

sync.Map 有如下幾個明顯的優(yōu)勢:

  • 并發(fā)安全
  • sync.Map 不需要使用 make 初始化,直接使用 myMap := sync.Map{} 即可使用 sync.Map 里面的方法

sync.Map 涉及的方法

見名知意

Store

存入 key 和value

Load

取出 某個key 對應的 value

LoadOrStore

取出 并且 存入 2個操作

Delete

刪除key 和 對應的 value

Range

遍歷所有key 和 對應的 value

總結

  • 通道是什么,通道的種類
  • 無緩沖,有緩沖,單向通道具體對應什么
  • 對于通道的具體實踐
  • 分享了關于通道的異常情況整理
  • 簡單分享了sync包的使用

以上就是GO語言中通道和sync包的使用教程分享的詳細內容,更多關于GO通道 sync包的資料請關注腳本之家其它相關文章!

相關文章

  • golang time包下定時器的實現方法

    golang time包下定時器的實現方法

    定時器的實現大家應該都遇到過,最近在學習golang,所以下面這篇文章主要給大家介紹了關于golang time包下定時器的實現方法,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-12-12
  • 從錯誤中學習改正Go語言六個壞習慣提高編程技巧

    從錯誤中學習改正Go語言六個壞習慣提高編程技巧

    這篇文章主要為大家介紹了從錯誤中學習改正Go語言五個壞習慣提高編程技巧示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-05-05
  • 解決golang 反射interface{}做零值判斷的一個重大坑

    解決golang 反射interface{}做零值判斷的一個重大坑

    這篇文章主要介紹了解決golang 反射interface{}做零值判斷的一個重大坑,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Golang控制協(xié)程執(zhí)行順序方法詳解

    Golang控制協(xié)程執(zhí)行順序方法詳解

    這篇文章主要介紹了Golang控制協(xié)程執(zhí)行順序的方法,Golang的語法和運行時直接內置了對并發(fā)的支持。Golang里的并發(fā)指的是能讓某個函數獨立于其他函數運行的能力
    2022-11-11
  • GoLang strings.Builder底層實現方法詳解

    GoLang strings.Builder底層實現方法詳解

    自從學習go一個月以來,我多少使用了一下strings.Builder,略有心得。你也許知道它,特別是你了解bytes.Buffer的話。所以我在此分享一下我的心得,并希望能對你有所幫助
    2022-10-10
  • Go實現數據脫敏的方案設計

    Go實現數據脫敏的方案設計

    在一些常見的業(yè)務場景中可能涉及到用戶的手機號,銀行卡號等敏感數據,對于這部分的數據經常需要進行數據脫敏處理,就是將此部分數據隱私化,防止數據泄露,所以本文給大家介紹了Go實現數據脫敏的方案設計,需要的朋友可以參考下
    2024-05-05
  • Golang 1.16 中 Modules的主要變化更新

    Golang 1.16 中 Modules的主要變化更新

    這篇文章主要介紹了Golang 1.16 中 Modules的主要變化更新,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-02-02
  • golang中time包之時間間隔格式化和秒、毫秒、納秒等時間戳格式輸出的方法實例

    golang中time包之時間間隔格式化和秒、毫秒、納秒等時間戳格式輸出的方法實例

    時間和日期是我們編程中經常會用到的,下面這篇文章主要給大家介紹了關于golang中time包之時間間隔格式化和秒、毫秒、納秒等時間戳格式輸出的方法實例,需要的朋友可以參考下
    2022-08-08
  • 一文帶你熟悉Go語言中的for循環(huán)

    一文帶你熟悉Go語言中的for循環(huán)

    這篇文章主要和大家分享一下Go語言中for循環(huán)的定義與使用,文中的示例代碼講解詳細,對我們學習Go語言有一定的幫助,需要的可以參考一下
    2022-11-11
  • Go語言實現猜數字小游戲

    Go語言實現猜數字小游戲

    這篇文章主要為大家詳細介紹了Go語言實現猜數字小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-10-10

最新評論