關于學習Go語言的并發編程

開始之前,介紹一下?最近很火的開源技術,低代碼。

作為一種軟件開發技術逐漸進入了人們的視角里,它利用自身獨特的優勢占領市場一角——讓使用者可以通過可視化的方式,以更少的編碼,更快速地構建和交付應用軟件,極大程度地降低了軟件的開發、配置、部署和培訓成本。

應用地址:https://www.jnpfsoft.com 開發語言:Java/.net

這是一個基于 Java Boot/.Net Core 構建的簡單、跨平臺快速開發框架。前后端封裝了上千個常用類,方便擴展;采用微服務、前后端分離架構,集成了代碼生成器,支持前后端業務代碼生成,滿足快速開發;框架集成了表單、報表、圖表、大屏等各種常用的 Demo 方便直接使用;后端框架支持 Vue2、Vue3,平臺即可私有化部署,也支持 K8S 部署。

關于并發

Go 語言的創始人Rob Pike?曾說過:并行關乎執行,并發關乎結構。他認為:
? 并發是一種程序設計方法:將一個程序分解成多個小片段,每個小片段獨立執行;并發程序的小片段之間可以通過通信相互協作。
? 并行是有關執行的,它表示同時進行一些計算任務。

程序小片段之間通訊不同語言實現不同,比如:傳統語言Java使用共享內存方式達到線程之間通訊,而Go語言channel來進行通訊。

原生線程、Java線程、Goroutine

Java中的多線程,由 JVM 在 Java 堆中分配內存來存儲線程的相關信息,包括線程棧、程序計數器等。當需要執行 Java 線程時,它會向操作系統請求分配一個或多個原生線程(例如 POSIX 線程或 Windows 線程),操作系統分配成功后,JVM 會將 Java 線程與這些原生線程進行映射,并建立關聯,并在需要時將 Java 線程的狀態同步到相應的原生線程中。

由此可以看出,Java線程和原生線程1:1對應,由操作系統(OS)調度算法執行,該并發以下特點:

  • 線程棧默認空間大且不支持動態伸縮,Java 默認最小都是1MB,Linux 默認 8MB;
  • 線程切換創建、銷毀以及線程間上下文切換的代價都較大。
  • 線程通過共享內存進行通訊,

POSIX線程(Pthreads)是C函數、類型和常數的集合,用于創建和管理線程。它是POSIX標準的一個子集,提供在BeagleBone Black上使用C/C++應用程序實現線程所需的一切。

原生線程就是操作系統線程或叫系統線程。

Go語言引入用戶層輕量級線程(Goroutine),它由Go運行時負責調度。Goroutine相比傳統操作系統線程而言有如下優勢。

  • 資源占用小,每個Goroutine的初始棧大小僅為2KB,且支持動態伸縮,避免內存浪費;
  • 由Go運行時而不是操作系統調度,goroutine上下文切換代價較小;
  • 內置channel作為goroutine間通信原語,為并發設計提供強大支撐。

了解Go調度原理

Go 語言實現了調度器(scheduler),它負責將 goroutine 分配到原生線程上執行。

G-P-M模型

Go 語言中的調度模型(G-P-M模型)它包含了三個重要組件:G(goroutine)、P(processor)、M(machine)。

GPM

  • G(goroutine):一個執行單元,這里也就是 goroutine,它包含了執行代碼所需的信息,比如棧空間、程序計數器等。
  • P(processor):P 一個邏輯處理器,它負責執行 goroutine。每個 P 維護了一個 goroutine 隊列,它可以將 goroutine 分配到 M(系統線程)上執行。P 的數量由 GOMAXPROCS 環境變量決定,默認值為 CPU 的邏輯核心數。
  • M(machine):一個系統線程(machine),它負責執行 goroutine 的真正計算工作。M 與操作系統的線程直接綁定,負責實際的計算任務,比如執行 goroutine 的函數、系統調用等。Go 語言的調度器會將多個 goroutine 映射到少量的系統線程上執行。

搶占式調度

在上面模型中,如果某個G處于死循環或長時間執行(比如:進行系統調用,IO操作),那么P隊列里面的G就長時間得不到執行,為了解決此問題,需要使用搶占式調度。

Java 中有以下兩種搶占式調度算法

  1. 優先級調度(Priority Scheduling)

    • 每個線程都有一個優先級,高優先級的線程會比低優先級的線程更容易獲得CPU的執行權(注意:設置了優先級不是絕對優先執行,只是概率上高)。
    • 在Java中,線程的優先級范圍是從Thread.MIN_PRIORITY(1)到Thread.MAX_PRIORITY(10),默認是Thread.NORM_PRIORITY(5)。
  2. 時間片輪轉調度(Round Robin Scheduling)

    • 每個線程被分配一個固定的時間片,當該線程的時間片用完時,操作系統會暫停它的執行,將CPU控制權交給下一個線程。
    • 在Java中,時間片輪轉調度通過yield()方法來實現。當線程調用yield()時,它就會主動放棄CPU的執行權,讓其他線程有機會執行。

Go 語言與Java搶占調度不同,Java是實際上是操作系統時間片輪轉調度,發生在內核層。Go 搶占調度是發生在用戶層,由 Go 運行時管理,通過軟件定時器和搶占點來實現搶占。

Go 程序啟動時會創建一個線程(稱為監控線程),該線程運行一個內部函數?sysmon?,用來進行系統監控任務,如垃圾回收、搶占調度、監視死鎖等。這個函數在后臺運行,確保 Go 程序的正常運行。

func main() {...if GOARCH != "wasm" { // 系統棧上的函數執行systemstack(func() {  newm(sysmon, nil, -1) // 用于創建新的 M(機器,代表一個操作系統線程)。})} ...
}

sysmon?每20us~10ms啟動一次,大體工作:

  • 釋放閑置超過5分鐘的span物理內存;
  • 如果超過2分鐘沒有垃圾回收,強制執行;
  • 將長時間未處理的netpoll結果添加到任務隊列;
  • 向長時間運行的G任務發出搶占調度;
  • 收回因syscall長時間阻塞的P。

具體來說,以下情況會觸發搶占式調度:

  1. 系統調用:當一個 goroutine 執行系統調用時,調度器會將該 goroutine 暫停,并將處理器分配給其他可運行的 goroutine。一旦系統調用完成,被暫停的 goroutine 可以繼續執行。
  2. 函數調用:當一個 goroutine 調用一個阻塞的函數(如通道的發送和接收操作、鎖的加鎖和解鎖操作等)時,調度器會將該 goroutine 暫停,并將處理器分配給其他可運行的 goroutine。一旦被阻塞的函數可以繼續執行,被暫停的 goroutine 可以繼續執行。
  3. 時間片耗盡:每個 goroutine 在運行一段時間后都會消耗一個時間片。當時間片耗盡時,調度器會將當前正在運行的 goroutine 暫停,并將處理器分配給其他可運行的 goroutine。被暫停的 goroutine 將會被放入到就緒隊列中,等待下一次調度。

GO并發模型

Go 使用 CSP(Communicating Sequential Processes,通信順序進程)并發編程模型,該模型由計算機科學家 Tony Hoare 在 1978 年提出。

在Go中,針對CSP模型提供了三種并發原語:

  • goroutine:對應CSP模型中的P(原意是進程,在這里也就是goroutine),封裝了數據的處理邏輯,是Go運行時調度的基本執行單元。
  • channel:對應CSP模型中的輸入/輸出原語,用于goroutine之間的通信和同步。
  • select:用于應對多路輸入/輸出,可以讓goroutine同時協調處理多個channel操作。

Go 奉行“不要通過共享內存來通信,而應通過通信來共享內存。”,也就是推薦通過channel來傳遞值,讓goroutine相互通訊協作。

channel 分為無緩沖和有緩沖,使用通道時遵循以下規范:

  1. 在無緩沖通道上,每一次發送操作都有對應匹配的接收操作。
  2. 對于從無緩沖通道進行的接收,發生在對該通道進行的發送完成之前。
  3. 對于帶緩沖的通道(緩存大小為C),通道中的第K個接收完成操作發生在第K+C個發送操作完成之前。
  4. 如果將C=0就是無緩沖的通道,也就是第K個接收完成在第K個發送完成之前。
func sender(ch chan<- int, done chan<- bool) {fmt.Println("Sending...")ch <- 42 // 發送數據到無緩沖通道fmt.Println("Sent")done <- true // 發送完成信號
}func receiver(ch <-chan int, done <-chan bool) {<-done // 等待發送操作完成信號fmt.Println("Receiving...")val := <-ch // 從無緩沖通道接收數據fmt.Println("Received:", val)
}func main() {ch := make(chan int) // 創建無緩沖通道done := make(chan bool) // 用于發送操作完成信號go sender(ch, done)   // 啟動發送goroutinego receiver(ch, done) // 啟動接收goroutinetime.Sleep(2 * time.Second) // 等待一段時間以觀察結果
}

有緩沖通道

func sender(ch chan<- int) {for i := 0; i < 5; i++ {fmt.Println("Sending:", i)ch <- i // 發送數據到通道fmt.Println("Sent:", i)}close(ch)
}func receiver(ch <-chan int) {for {val, ok := <-ch // 從通道接收數據if !ok {fmt.Println("Channel closed")return}fmt.Println("Received:", val)time.Sleep(1 * time.Second) // 模擬接收操作耗時}
}func main() {ch := make(chan int, 2) // 創建帶緩沖大小為2的通道go sender(ch)   // 啟動發送goroutinego receiver(ch) // 啟動接收goroutinetime.Sleep(10 * time.Second) // 等待一段時間以觀察結果
}

Go并發場景

并行計算

利用goroutine并發執行任務,加速計算過程。

// calculateSquare 是一個計算數字平方的函數,它模擬了一個耗時的計算過程。
func calculateSquare(num int, resultChan chan<- int) {time.Sleep(1 * time.Second) // 模擬耗時計算resultChan <- num * num
}func main() {nums := []int{1, 2, 3, 4, 5}resultChan := make(chan int)// 啟動多個goroutine并發計算數字的平方for _, num := range nums {go calculateSquare(num, resultChan)}// 從通道中接收計算結果并打印for range nums {result := <-resultChanfmt.Println("Square:", result)}close(resultChan)
}

IO密集型任務

在處理IO密集型任務時,可以使用goroutine和channel實現并發讀寫操作,提高IO效率。

// fetchURL 函數用于獲取指定URL的內容,并將結果發送到通道resultChan中。
func fetchURL(url string, resultChan chan<- string) {resp, err := http.Get(url)if err != nil {resultChan <- fmt.Sprintf("Error fetching %s: %s", url, err)return}defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)if err != nil {resultChan <- fmt.Sprintf("Error reading response from %s: %s", url, err)return}resultChan <- string(body)
}func main() {urls := []string{"https://example.com", "https://example.org", "https://example.net"}resultChan := make(chan string)// 啟動多個goroutine并發獲取URL的內容for _, url := range urls {go fetchURL(url, resultChan)}// 從通道中接收結果并打印for range urls {result := <-resultChanfmt.Println("Response:", result)}close(resultChan)
}

并發數據處理

對于需要同時處理多個數據流的情況,可以使用goroutine和channel實現并發數據處理,例如數據流的合并、拆分、過濾等操作。

// processData 函數用于處理從dataStream中接收的數據,并將處理結果發送到resultChan中。
func processData(dataStream <-chan int, resultChan chan<- int) {for num := range dataStream {resultChan <- num * 2 // 假設處理數據是將數據乘以2}
}func main() {dataStream := make(chan int)resultChan := make(chan int)// 產生數據并發送到dataStream中go func() {for i := 1; i <= 5; i++ {dataStream <- i}close(dataStream)}()// 啟動goroutine并發處理數據go processData(dataStream, resultChan)// 從通道中接收處理結果并打印for range dataStream {result := <-resultChanfmt.Println("Processed Data:", result)}close(resultChan)
}

并發網絡編程

編寫網絡服務器或客戶端時,可以利用goroutine處理每個連接,實現高并發的網絡應用。

// handler 是一個HTTP請求處理函數,它會向客戶端發送"Hello, World!"的響應。
func handler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello, World!")
}func main() {// 注冊HTTP請求處理函數http.HandleFunc("/", handler)// 啟動HTTP服務器并監聽端口8080go http.ListenAndServe(":8080", nil)fmt.Println("Server started on port 8080")// 使用select{}使主goroutine保持運行狀態,以便HTTP服務器能夠處理請求select {}
}

定時任務和周期性任務

// task 是一個需要定時執行的任務函數。
func task() {fmt.Println("Task executed at:", time.Now())
}func main() {ticker := time.NewTicker(1 * time.Second)defer ticker.Stop()// 循環等待定時器的觸發并執行任務for {select {case <-ticker.C:task()}}
}

工作池

通過創建一組goroutine來處理任務池中的任務,可以有效地控制并發數量,適用于需要限制并發的情況。

// worker 是一個工作函數,它會從jobs通道中接收任務,并將處理結果發送到results通道中。
func worker(id int, jobs <-chan int, results chan<- int) {for job := range jobs {fmt.Printf("Worker %d started job %d\n", id, job)time.Sleep(1 * time.Second) // 模擬工作時間fmt.Printf("Worker %d finished job %d\n", id, job)results <- job * 2 // 假設工作的結果是輸入的兩倍}
}func main() {const numJobs = 10const numWorkers = 3jobs := make(chan int, numJobs)    // 緩沖channel用于發送任務results := make(chan int, numJobs) // 用于接收任務結果// 啟動多個worker goroutinevar wg sync.WaitGroupfor i := 1; i <= numWorkers; i++ {wg.Add(1)go func(id int) {defer wg.Done()worker(id, jobs, results)}(i)}// 發送任務到jobs channelfor j := 1; j <= numJobs; j++ {jobs <- j}close(jobs) // 關閉jobs channel// 等待所有worker完成并收集結果go func() {wg.Wait()close(results)}()// 從通道中接收處理結果并打印for result := range results {fmt.Println("Result:", result)}
}

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

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

相關文章

【數據結構】直接選擇排序詳解!

文章目錄 1.直接選擇排序 1.直接選擇排序 &#x1f427; begin 有可能就是 maxi &#xff0c;所以交換的時候&#xff0c;要及時更新 maxi &#x1f34e; 直接選擇排序是不穩定的&#xff0c;例如&#xff1a; 9 [9] 5 [5]&#xff0c;排序后&#xff0c;因為直接選擇排序是會…

Debug-012-el-popover 使用 doClose() 關閉窗口不生效的處理方案

前言&#xff1a; 今天上午碰見一個非常奇怪的情況&#xff1a;一樣的方法實現的功能&#xff0c;效果卻不一樣。 兩個頁面都是使用的doClose()去關閉的el-popover&#xff0c;其中有一個就是不生效&#xff0c;找不同找了半天&#xff0c;始終不得其解。請看效果吧&#xff1…

Day 5:2785. 將字符串中的元音字母排序

Leetcode 2785. 將字符串中的元音字母排序 給你一個下標從 0 開始的字符串 s &#xff0c;將 s 中的元素重新 排列 得到新的字符串 t &#xff0c;它滿足&#xff1a; 所有輔音字母都在原來的位置上。更正式的&#xff0c;如果滿足 0 < i < s.length 的下標 i 處的 s[i] …

【第5章】SpringBoot整合Druid

文章目錄 前言一、啟動器二、配置1.JDBC 配置2.連接池配置3. 監控配置 三、配置多數據源1. 添加配置2. 創建數據源 四、配置 Filter1. 配置Filter2. 可配置的Filter 五、獲取 Druid 的監控數據六、案例1. 問題2. 引入庫3. 配置4. 配置類5. 測試類6. 測試結果 七、案例 ( 推薦 )…

理解磁盤分區與管理:U啟、PE、DiskGenius、MBR與GUID

目錄 U啟和PE的區別: U啟(U盤啟動): PE(預安裝環境)&#xff1a; 在DiskGenius中分區完成之后是否還需要格式化&#xff1a; 1.建立文件系統&#xff1a; 2.清除數據&#xff1a; 3.檢查并修復分區&#xff1a; 分區表格式中&#xff0c;MBR和GUID的區別&#xff1a; 1…

移動端開發 筆記01

目錄 01 移動端的概述 02 移動端的視口標簽 03 開發中的二倍圖 04 流式布局 05 彈性盒子布局 01 移動端的概述 移動端包括:手機 平板 便攜式設備 目前主流的移動端開發: 安卓設備 IOS設備 只要移動端支持瀏覽器 那么就可以使用瀏覽器開發移動端項目 開發移動端 使用…

怎么看外國的短視頻:四川鑫悅里文化傳媒有限公司

怎么看外國的短視頻&#xff1a;跨文化視角下的觀察與思考 隨著全球化進程的加速和網絡技術的飛速發展&#xff0c;外國短視頻逐漸走進了我們的視野。這些來自不同文化背景、語言體系和審美觀念的短視頻作品&#xff0c;為我們打開了一扇了解世界的窗口。然而&#xff0c;如何…

golang中的md5、sha256數據加密文件md5/sha256值計算步驟和運行內存圖解

在go語言中對數據計算一個md5&#xff0c;或sha256和其他語言 如java, php中的使用方式稍有不同&#xff0c; 那就是要加密的數據必須通過流的形式寫入到你創建的Hash對象中。 Hash數據加密步驟 1. 先使用對應的加密算法包中的New函數創建一個Hash對象&#xff0c;(這個也就是…

leetCode. 85. 最大矩形

leetCode. 85. 最大矩形 部分參考上一題鏈接 leetCode.84. 柱狀圖中最大的矩形 此題思路 代碼 class Solution { public:int largestRectangleArea( vector<int>& h ) {int n h.size();vector<int> left( n ), right( n );stack<int> st;// 求每個矩形…

vue/uniapp 企業微信H5使用JS-SDK

企業微信H5需要我們使用一些SDK方法如獲取外部聯系人userid 獲取當前外部聯系人userid 使用SDK前提是如何通過config接口注入權限驗證配置 使用說明 - 接口文檔 - 企業微信開發者中心 當前項目是vue項目&#xff0c;不好直接使用 引入JS文件&#xff0c;但我們可以安裝依賴…

使用nexus搭建的docker私庫,定期清理無用的鏡像,徹底釋放磁盤空間

一、背景 我們使用nexus搭建了docker鏡像&#xff0c;隨著推送的鏡像數量越來越多&#xff0c;導致nexus服務器的磁盤空間不夠用了。于是&#xff0c;我們急需先手動刪除一些過期的鏡像&#xff0c;可發現磁盤空間并沒有釋放。 那么&#xff0c;如何才能徹底釋放掉呢&#xff…

FreeRTOS學習 -- 任務 API 函數

函數 uxTaskPriorityGet() 此函數用來查詢指定任務的優先級&#xff0c;要使用此函數的話宏 INCLUDE_uxTaskPriorityGet 應該定義為 1。 函數 vTaskPrioritySet() 此函數用于改變某一個任務的任務優先級&#xff0c;要 使 用 此 函 數 的 話 宏 INCLUDE_vTaskPrioritySet 應…

一維數組操作(GOC常考類型)答案

第1題 宇航局招聘 時限&#xff1a;1s 空間&#xff1a;256m 宇航局準備招收一批科研人員從事月球探索的航空科研工作。這個職位來了很多應聘者&#xff0c;宇航局對眾多應聘者進行綜合素質考試&#xff0c;最終會選出x名綜合得分排名靠前應聘者。目前考試已經結束了&a…

Golang | Leetcode Golang題解之第102題二叉樹的層序遍歷

題目&#xff1a; 題解&#xff1a; func levelOrder(root *TreeNode) [][]int {ret : [][]int{}if root nil {return ret}q : []*TreeNode{root}for i : 0; len(q) > 0; i {ret append(ret, []int{})p : []*TreeNode{}for j : 0; j < len(q); j {node : q[j]ret[i] …

Java面試精粹:高級問題與解答集錦(一)

Java 面試問題及答案 1. 什么是Java的垃圾回收機制&#xff0c;它如何工作&#xff1f; 答案&#xff1a; Java的垃圾回收機制是一種自動內存管理功能&#xff0c;用于回收不再被應用程序使用的對象所占用的內存。它通過垃圾收集器&#xff08;Garbage Collector&#xff0c;…

js數據類型顯隱式轉換

在JavaScript中&#xff0c;數據類型的轉換可以分為兩種主要類型&#xff1a;顯式類型轉換&#xff08;Explicit Type Conversion&#xff09;和隱式類型轉換&#xff08;Implicit Type Conversion 或 Type Coercion&#xff09;。 顯式類型轉換&#xff08;Explicit Type Con…

React18+TypeScript搭建通用中后臺項目實戰02 整合 antd 和 axios

配置路徑別名 tsconfig.json {"compilerOptions": {"target": "ES2020","useDefineForClassFields": true,"lib": ["ES2020","DOM","DOM.Iterable"],"module": "ESNext&quo…

磁盤分區和掛載

磁盤分區和掛載 一、磁盤 業務層面&#xff1a;滿足一定的需求所是做的特定操作 硬盤是什么以及硬盤的作用 硬盤&#xff1a;計算器的存儲設備&#xff0c;一個或者多個磁性的盤片做成&#xff0c;可以在盤片上進行數據的讀寫 連接方式&#xff1a;內部設備&#xff0c;外…

深度揭秘:藍海創意云渲染農場的五大特色功能

在當今數字化時代&#xff0c;影視制作、效果圖設計等領域對于高質量的渲染需求日益增長。在這個背景下&#xff0c;云渲染平臺成為了行業中不可或缺的一部分&#xff0c;它為用戶提供了高效、靈活的渲染解決方案。藍海創意云渲染農場https://www.vsochina.com/cn/render藍海創…

軟件需求分析和軟件原型開發是一會事情嗎?

軟件需求分析和軟件原型開發是軟件開發過程中的兩個重要環節&#xff0c;它們各自承擔著不同的任務&#xff0c;但又緊密相連&#xff0c;共同影響著軟件項目的成功。下面將詳細解釋這兩個環節的定義、目的以及它們之間的關系。 一、軟件需求分析 定義&#xff1a;軟件需求分析…