【golang長途旅行第30站】channel管道------解決線程競爭的好手

channel

為什么需要channel

使用全局變量加鎖同步來解決goroutine的競爭,可以但不完美

  • 難以精確控制等待時間?(主線程無法準確知道所有 goroutine 何時完成)。

  • 全局變量容易引發競態條件?(即使加鎖,代碼復雜度也會增加)。

  • 不夠優雅,Go 更推薦使用 ?channel? 進行通信。

channel基本介紹

  • ?channel(通道)?? 是一種用于 ?goroutine(協程)之間通信和同步? 的機制。
  • channel的本質是一個隊列,遵循先進先出(FIFO)
  • channel是有類型的,一種channel只能存儲類型與該channel的類型相同的數據
  • channel是線程安全的,不需要加鎖
  • channel是引用類型,必須初始化make后才能寫入數據,未初始化的channel是nil

channel快速入門

channel的聲明

var 變量名 chan 數據類型

channel的初始化

var myChan chan int = make(chan int, 2)

發送數據到channel

myChan <- 10
myChan <- 20

此處注意:channel不像map等會自動擴容,channel接收的數據數的最大值在make函數里已經自定義完成了,容量就是一直這么大,不會改變。

從channel接收數據

num := <-myChan
fmt.Println(num)

輸出結果:10
此處接收數據不能超出myChan現有的數據量,也就是myChan的長度。

channel的細節

  1. channel中只能存放指定的數據類型

  2. channle的數據放滿后,就不能再放入了

  3. channel放滿后,如果從channel取出數據后,可以繼續放入

<-myChan也是取出了數據,只是沒有被接收罷了

  1. 在沒有使用協程的情況下,如果channel數據取完了,再取,就會報deadlock
易錯點

如果想在channel中輸入多樣的數據類型,就將channel聲明成空接口interface{}的類型
代碼示例:
func main() {
myChan := make(chan interface{}, 3)
myChan <- 10
myChan <- “sa”
person := Person{“xxx”}
myChan <- person
<-myChan
<-myChan
person2 := <-myChan
// fmt.Printf(“person2的type%T,值%v,name%v”, person2, person2, person2.Name)
person3 := person2.(Person)
fmt.Printf(“person3的type:%T,值:%v,name:%v”, person3, person3, person3.Name)
}
輸出結果:person3的typemain.Person,值{xxx},namexxx

唉,為什么要person3 := person2.(Person)呢,直接用fmt.Printf(“person2的type%T,值%v,name%v”, person2, person2, person2.Name)不好嗎?
當然不好啦,使用這個代碼會報錯,會說person2.Name undefined
為什么呢?
因為person2是從channel中讀取的interface{}類型,雖然實際值是Person類型,但編譯器不知道其具體類型,因此無法直接訪問Name字段。
所以要通過person3 := person2.(Person),提取一個類型斷言后的值,將其轉換為具體的Person類型,然后才能訪問其字段

channel的關閉

發送方可以關閉 channel,表示不再發送數據
內置函數:close(ch)
??關閉后,仍然可以接收數據?(直到 channel 為空)。
??向已關閉的 channel 發送數據會 panic。

channel的遍歷

  1. 通過 for-range 遍歷
    代碼示例:
    func main() {
    myChan := make(chan int, 3)
    myChan <- 10
    myChan <- 30
    myChan <- 20
    close(myChan)
    for v := range myChan {
    fmt.Printf(“%v\n”, v)
    }
    }
    輸出結果:
    10
    30
    20

for range會一直從 ch接收數據,直到 ch被關閉。
如果 ch未關閉,for range會一直阻塞,可能導致死鎖。

手動檢查 channel 是否關閉
可以用 value, ok := <-ch的方式檢查 channel 是否關閉, 如果 channel 關閉,ok 為 false

  1. 傳統for循環
    func main() {
    myChan := make(chan int, 3)
    myChan <- 10
    myChan <- 30
    myChan <- 20
    len := len(myChan)
    for i := 0; i < len; i++ {
    fmt.Println(<-myChan)
    }
    }

也可以正常有序輸出,輸出結果與for-range一致

channel的阻塞

阻塞是指 goroutine 在 channel 操作上等待,但不會導致整個程序卡死。?

  1. 從空的 channel 接收數據
  2. 向已滿的緩沖 channel 發送數據
  3. 讀比寫的操作慢,導致出現(2)情況
  4. 寫比讀的操作慢,導致出現(1)情況

channel的死鎖

死鎖是指所有 goroutine 都在等待對方釋放資源,導致程序無法繼續執行。

  1. 所有 goroutine 都在等待 channel
  2. 未關閉 channel 導致 for range死鎖

使用細節

  1. channel可以聲明為只讀,或者只寫性質

此處只讀只寫只是一種屬性,并不會改變channel的類型,該是chan int 就還是chan int
chan<- int 是只寫
<-chan int 是只讀

代碼示例:
package main

import (
“fmt”
“math/rand”
“time”
)

// 只寫通道:用于發送訂單
func orderProducer(orderChan chan<- int, doneChan chan<- struct{}) {
defer close(orderChan) // 生產結束后關閉訂單通道

for i := 1; i <= 5; i++ {orderID := rand.Intn(1000) + 1000 // 模擬生成訂單號fmt.Printf("📦 生成訂單 #%d (ID: %d)\n", i, orderID)orderChan <- orderIDtime.Sleep(time.Second) // 模擬生產間隔
}doneChan <- struct{}{} // 發送完成信號

}

// 只讀通道:用于處理訂單
func orderProcessor(orderChan <-chan int, doneChan chan<- struct{}) {
for orderID := range orderChan { // 自動檢測通道關閉
processTime := time.Duration(rand.Intn(1500)) * time.Millisecond
fmt.Printf(“處理訂單 ID: %d (耗時: %v)\n”, orderID, processTime)
time.Sleep(processTime)
}

doneChan <- struct{}{} // 發送完成信號

}

func main() {
// 初始化通道(帶緩沖)
orderChan := make(chan int, 3) // 訂單通道(緩沖3個訂單)
doneChan := make(chan struct{}, 2) // 控制通道(緩沖2個信號)

// 啟動服務
go orderProducer(orderChan, doneChan) // 訂單生產(只寫)
go orderProcessor(orderChan, doneChan) // 訂單處理(只讀)// 等待兩個服務完成
for i := 0; i < 2; i++ {<-doneChan
}
fmt.Println("所有訂單處理完成")

}

這段代碼中,main函數中定義的orderChan是一個chan int 類型,但他可以同時被使用在只讀和只寫的函數里,這就很大程度上的便于代碼的管理,防止誤操作。

  1. select解決 channel 阻塞問題
    日常中,難以準確判斷讀取/寫入與關閉時機難以掌握,所以提出select,雖然select還是無法關閉channel,但是能防止防止讀取/寫入時的無限等待
    代碼示例
    for{
    select {
    case msg := <-ch1:
    fmt.Println(“收到 ch1:”, msg)
    case msg := <-ch2:
    fmt.Println(“收到 ch2:”, msg)
    case <-time.After(3 * time.Second): // 超時控制
    fmt.Println(“讀取超時”)
    return
    }
    }

如果多個 case 的 channel 同時就緒(例如多個 channel 都有數據可讀),select?會隨機選擇一個執行?(公平調度,避免饑餓問題)
select?自動忽略未就緒的 channel?(無論是否關閉),無需手動處理。
此處的ch1哪怕沒有關閉,也不會報錯,而是在無法從ch1取到值后,會暫時將這個case不考慮在執行case內
?每次執行 select時都會重新檢查所有 case的就緒狀態
還有,最后的return不能使用break代替,因為break只能退出select不能退出for循環,所以相當于重新開始了
return其實還可以用之前提到的label來代替,就是給這個for循環一個標簽,然后break label就好了 (但是這種方式并不建議,可讀性較差)

  1. recover來防止出現因為一個線程的錯誤導致其它線程無法進行
    原錯誤代碼:
    package main

import (
“fmt”
“time”
)

// 1. 循環打印 “hello,world”
func sayHello() {
for i := 0; i < 10; i++ {
fmt.Println(“hello,world”)
time.Sleep(1 * time.Second)
}
}

// 2. 測試未初始化的 map(會觸發 panic)
func test() {
var myMap map[int]string
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
myMap[0] = “golang” // error: 未初始化的 map 賦值會導致 panic
}

// 3. 主函數(并發執行)
func main() {
go sayHello() // 啟動協程
go test() // 啟動協程(會崩潰)

// 主線程繼續執行
for i := 0; i < 10; i++ {fmt.Printf("main() ok=%d\n", i)time.Sleep(1 * time.Second)
}

}
輸出結果:
main() ok=0
hello,world
panic: assignment to entry in nil map

報了panic錯誤,主線程并沒有正常運行

修改代碼:
func test() {
var myMap map[int]string
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
myMap[0] = “golang” // error: 未初始化的 map 賦值會導致 panic
}

將錯誤的test函數加上錯誤捕獲,異常處理

輸出結果:
hello,world
assignment to entry in nil map
main() ok=0
main() ok=1
hello,world
hello,world
main() ok=2
hello,world
main() ok=3
hello,world
main() ok=4
hello,world
main() ok=5
main() ok=6
hello,world
hello,world
main() ok=7
main() ok=8
hello,world
hello,world
main() ok=9

即使仍是錯誤,也依舊不影響其它線程

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/94281.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/94281.shtml
英文地址,請注明出處:http://en.pswp.cn/web/94281.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

蘋果XR芯片介紹

蘋果的 XR 芯片技術主要體現在 A 系列、M 系列處理器以及專為空間計算設計的 R1 協處理器中。以下從技術架構、產品迭代和綜合對比三個維度展開分析&#xff1a;一、技術架構解析1. A 系列芯片&#xff08;以 A12 Bionic 為例&#xff09;制程工藝&#xff1a;7nm&#xff08;臺…

達夢數據庫巡檢常用SQL(三)

達夢數據庫巡檢常用SQL(三) 數據庫SQL運行檢查 數據庫SQL運行檢查 死鎖的事務情況: SELECT TO_CHAR(HAPPEN_TIME,YYYY-MM-DD HH24:MI:SS) HAPPEN_TIME,SQL_TEXT FROM V$DEADLOCK_HISTORY WHERE HAPPEN_TIME >DATEADD(DAY,-30,

基于SpringBoot的校園周邊美食探索及分享平臺

1. 項目簡介 項目名稱&#xff1a;校園周邊美食探索及分享平臺 項目背景&#xff1a;針對校園師生對周邊美食信息的需求&#xff0c;構建一個集美食推薦、鑒賞、評論互動及社交功能于一體的平臺&#xff0c;幫助用戶發現優質美食資源并進行分享交流。 主要目標&#xff1a; 提供…

Go數據結構與算法-常見的排序算法

雖然看過別人寫了很多遍&#xff0c;而且自己也寫過很多遍&#xff08;指的是筆記&#xff09;&#xff0c;但是還是要寫的就是排序算法。畢竟是初學Go語言&#xff0c;雖然之前寫過&#xff0c;但是還是打算再寫一遍。主要包括插入排序、選擇排序、冒泡排序、快速排序、堆排序…

第 6 篇:目標規則與負載均衡 - `DestinationRule` 詳解

系列文章:《Istio 服務網格詳解》 第 6 篇:目標規則與負載均衡 - DestinationRule 詳解 本篇焦點: 深入理解 DestinationRule 的核心作用:定義流量在到達目的地之后的行為。 詳細剖析其三大核心功能:服務子集 (Subsets), 流量策略 (Traffic Policy), TLS 設置。 動手實戰…

一個簡潔的 C++ 日志模塊實現

一個簡潔的 C 日志模塊實現 1. 引言 日志功能在軟件開發中扮演著至關重要的角色&#xff0c;它幫助開發者追蹤程序執行過程、診斷問題以及監控系統運行狀態。本文介紹一個使用 C 實現的輕量級日志模塊&#xff0c;該模塊支持多日志級別、線程安全&#xff0c;并提供了簡潔易用…

C語言---數據類型

文章目錄數據類型分類1. 基本類型 (Basic Types)a. 整數類型 (Integer Types)char (字符型)int (整型)short (短整型)long (長整型)long long (C99標準引入)圖片匯總b. 浮點類型 (Floating-Point Types)float (單精度浮點型)double (雙精度浮點型)long double (長雙精度浮點型)…

本搭建烏云漏洞庫

1.下載鏡像站文件&#xff0c;并拖入虛擬機 2.將bugs.rar解壓至網站根目錄下 /var/www/html 3.配置bugs/conn.php 4.在bugs下創建upload目錄&#xff0c;將10-14、15-a、15-b、16壓縮包文件解壓到該upload目錄 5.把wooyun.rar解壓到 /mysql/data/wooyun目錄下 6.配置hosts文件后…

Vmware虛擬機 處理器配置選項配置介紹

1. 處理器配置選項好&#x1f44c;&#xff0c;我來幫你逐一解讀 VMware 里 虛擬機處理器 這些選項的含義。 你截的圖里&#xff0c;主要有三塊內容&#xff1a; 處理器數量 每個處理器的內核數量 ©虛擬化引擎1?? 處理器數量 這是分配給虛擬機的 邏輯 CPU 插槽數。一般…

day40-tomcat

1.每日復盤與今日內容1.1復盤keepalived高可用配置搶占式與非搶占式腦裂keepalived處理Nginx掛掉1.2今日內容部署、安裝、配置tomcat(systemctl)Tomcat主配置文件部署靜態頁部署zrlog&#x1f35f;&#x1f35f;&#x1f35f;&#x1f35f;&#x1f35f;接入負載均衡掛載到NFS2…

【RA-Eco-RA4E2-64PIN-V1.0 開發板】步進電機的串口控制

【RA-Eco-RA4E2-64PIN-V1.0 開發板】步進電機的串口控制 本文介紹了 RA-Eco-RA4E2-64PIN-V1.0 開發板通過串口指令實現 28BYJ-48 步進電機旋轉角度和速度的精確控制的項目設計。 項目介紹 硬件連接&#xff1a;28BYJ-48 步進電機、ULN2003 驅動板、Jlink 調試器、供電電源等&am…

PiscCode基于 Mediapipe 的人體多模態關鍵點檢測與可視化系統 —— HumanMultiLandmarker 深度解析

一、引言 在計算機視覺領域&#xff0c;人體關鍵點檢測&#xff08;Human Pose Estimation&#xff0c;HPE&#xff09;一直是研究和應用的熱點方向之一。隨著深度學習與實時圖像處理技術的發展&#xff0c;人體姿勢估計已經從傳統的 2D 檢測走向了 3D 空間建模&#xff0c;并…

文獻閱讀筆記【物理信息機器學習】:Physics-informed machine learning

文獻閱讀筆記&#xff1a;Physics-informed machine learningSummaryResearch ObjectiveBackground / Problem Statement問題背景研究現狀需解決的問題問題出現的原因分析問題解決思路Method(s)問題建模作者解決問題的方法/算法1. 觀測偏差&#xff08;Observational Biases&am…

Linux服務環境搭建指南

實驗拓撲概述**實驗拓撲&#xff1a; APPSRV&#xff1a; 主機名&#xff1a;appsrv.example.com ip地址&#xff1a;192.168.100.10 網關&#xff1a;192.168.100.254 網卡為NAT模式 STORAGESRV&#xff1a; 主機名&#xff1a;storagesrv.example.com ip地址&#xff1a;192.…

[特殊字符] 數據庫知識點總結(SQL Server 方向)

一、數據庫基礎概念數據庫&#xff08;Database&#xff09;&#xff1a;存儲和管理數據的容器。數據表&#xff08;Table&#xff09;&#xff1a;以行和列形式組織數據。行&#xff08;Row&#xff09;&#xff1a;一條記錄。列&#xff08;Column&#xff09;&#xff1a;字…

【PSINS工具箱】MATLAB例程,二維平面上的組合導航,EKF融合速度、位置和IMU數據,4維觀測量

文章目錄關于工具箱程序簡介代碼概述核心功能與步驟運行結果MATLAB代碼關于工具箱 本文所述的代碼需要基于PSINS工具箱&#xff0c;工具箱的講解&#xff1a; PSINS初學指導&#xff1a;https://blog.csdn.net/callmeup/article/details/137087932 本文為二維平面上的定位&am…

MiMo-VL 技術報告

摘要 我們開源了 MiMo-VL-7B-SFT 和 MiMo-VL-7B-RL 兩個強大的視覺語言模型,它們在通用視覺理解和多模態推理方面均展現出最先進的性能。MiMo-VL-7B-RL 在 40 項評估任務中的 35 項上優于 Qwen2.5-VL-7B,并在 OlympiadBench 上獲得 59.4 分,超越了參數量高達 780 億的模型。…

CTFshow Pwn入門 - pwn 19

先看main函數&#xff1a;fclose(_bss_start) fclose(stdout) 關閉了默認fd1的輸出&#xff0c;所以system的結果無法直接看到。 思路&#xff1a; 輸出重定向。 ls 1>&0 ls >&0 ls >&2 ###三種寫法均可將輸出重定向到能回顯的終端并獲得一個新的交互…

Redis(以Django為例,含具體操作步驟)

簡介Redis&#xff08;Remote Dictionary Server&#xff09;是一個開源的內存數據結構存儲系統&#xff0c;支持多種數據結構&#xff08;如字符串、哈希、列表、集合、有序集合等&#xff09;&#xff0c;可用作數據庫、緩存或消息隊列。其核心特點包括&#xff1a;高性能&am…

瀏覽器解析網址的過程

問題瀏覽器解析網址的過程我的回答當你在瀏覽器地址欄輸入一個URL&#xff08;比如www.example.com&#xff09;并按下回車后&#xff0c;會發生以下一系列步驟&#xff1a;首先&#xff0c;瀏覽器會解析URL結構&#xff0c;確定要訪問的協議、域名和路徑。如果你沒有輸入協議部…