并發編程 - go版

1.并發編程基礎概念

進程和線程

    A. 進程是程序在操作系統中的一次執行過程,系統進行資源分配和調度的一個獨立單位。B. 線程是進程的一個執行實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。C.一個進程可以創建和撤銷多個線程;同一個進程中的多個線程之間可以并發執行。

并發和并行

    A. 多線程程序在一個核的cpu上運行,就是并發。B. 多線程程序在多個核的cpu上運行,就是并行。

并發

并發

并行

并行

goroutine 只是由官方實現的超級"線程池"。

每個實力4~5KB的棧內存占用和由于實現機制而大幅減少的創建和銷毀開銷是go高并發的根本原因。

進程:程序執行所用的內存空間。

線程:程序執行者。

協程:就是比線程更小的線程。(開銷小、體量小)

并發:多線程程序執行, 但是吧cpu只有一個核,一段時間內,只能執行一個線程。

并行:多線程程序執行,cpu有多個核,每個核對不同程序的調用是可以同時進行的。

為什么說go支持并發?

1.因為 go 中的 goroutine(由go在語言層面實現的線程池 ):

我們在類似于java語言層面要實現線程,

1.1首先創建一個線程來執行任務;

1.2 線程池的存在,就是里面有一組可復用線程,我們直接把任務交給線程池,它幫我們選出空閑線程來執行。在這個層面上,go已經語言層面已經遙遙領先,如次,簡化多線程程序開發。

2.以及協程: 一種比線程更小,可以被用戶調度的線程。看協程的定義我們知道,創建一個協程所用的開銷(創建、管理啥的)所用的內存是比線程更小。

3.還有就是其余編程語言并發模型是共享內存模型,比如java,線程通信通過共享內存實現的,我們無法保證操作是原子性的,因此需要設計同步機制,但是go通過channel來實現協程通信,就解決了如上問題。

因此,綜上所述,說go默認支持高并發。

?2.Goroutine

嗯,我們要在go語言啟用協程是十分簡單的。

原先。

func main() {hello()fmt.Println("main goroutine done")
}func hello() {fmt.Println("Hello Goroutine!")
}

把函數的執行交給協程來管理。

func main() {go hello()fmt.Println("main goroutine done")
}func hello() {fmt.Println("Hello Goroutine!")
}

PS : go 關鍵字,必須跟著函數調用。

但是,我們發現實際調用時,go 關鍵字后面的hello程序并未執行,這是因為main函數的執行,也是由一個協程來執行的。當main函數結束時,main 協程也會結束,在里面執行的協程也會強制終結。可能hello 協程,剛創建還沒執行就寄了。

因此,我們可以如下修改。延長main()函數的執行時間。

func main() {go hello()fmt.Println("main goroutine done")time.Sleep(time.Second)
}func hello() {fmt.Println("Hello Goroutine!")
}

按照官方的說法,協程(goroutine)的調度是由GPM控制的。GPM是go語言自己實現的一套調度系統。區別于操作系統調度OS線程。

  • 1.G很好理解,就是個goroutine的,里面除了存放本goroutine信息外 還有與所在P的綁定等信息。
  • 2.P管理著一組goroutine隊列,P里面會存儲當前goroutine運行的上下文環境(函數指針,堆棧地址及地址邊界),P會對自己管理的goroutine隊列做一些調度(比如把占用CPU時間較長的goroutine暫停、運行后續的goroutine等等,當自己的隊列消費完了就去全局隊列里取,如果全局隊列里也消費完了會去其他P的隊列里搶任務。
  • 3.M(machine)是Go運行時(runtime)對操作系統內核線程的虛擬, M與內核線程一般是一一映射的關系, 一個groutine最終是要放到M上執行的;

P與M一般也是一一對應的。他們關系是: P管理著一組G掛載在M上運行。當一個G長久阻塞在一個M上時,runtime會新建一個M,阻塞G所在的P會把其他的G 掛載在新建的M上。當舊的G阻塞完成或者認為其已經死掉時 回收舊的M。

因此,但從線程調度來講,相比其他語言(如:Java),go可以使用戶自己調度線程。

3.runtime包

runtime包提供和go運行時環境的互操作,如制go協程的函數。它也包括用于reflect包的低層次類型信息;參見reflect報的文檔獲取運行時類型系統的可編程接口。

runtime.Gosched()

Gosched使當前go協程放棄處理器,以讓其它go程運行。暫時停止該go協程的處理。它不會終結當前go協程,因此當前go協程未來會恢復執行。

func main() {go func(s string) {for i := 0; i < 2; i++ {fmt.Println(s)}}("world")// 主協程for i := 0; i < 2; i++ {fmt.Println("hello")runtime.Gosched()}
}

當我們運行此代碼時,執行一次 "hello"。之后中斷主協程,執行其他協程。之后,再次執行該go協程。

runtime.Goexit()

Goexit終止調用它的go協程。其它go協程不會受影響。Goexit會在終止該go協程前執行所有defer的函數。在程序的main go協程調用本函數,會終結 main函數的go協程,而不會讓main返回。因為main函數沒有返回,程序會繼續執行其它的go程。如果所有其它go協程都退出了,程序就會崩潰。

區別于,main函數執行完畢,main 協程結束。會自動終結函數內開啟的所有協程。

func main() {go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")// 結束協程runtime.Goexit()defer fmt.Println("C.defer")fmt.Println("B")}()fmt.Println("A")}()for {}
}

runtime.GOMAXPROCS()

GOMAXPROCS設置可同時執行的最大CPU數,并返回先前的設置。?若?n < 1,它就不會更改當前設置。本地機器的邏輯CPU數可通過?NumCPU?查詢。本函數在調度程序優化后會去掉。

我們看下面代碼: 用來驗證并行。

當我們設置go程序并發時占用的cpu核數為1時。我們得出的時間納秒差。

PS: 下面出現的差是不定的。

var atime int64
var btime int64func a() {for i := 1; i < 1000; i++ {fmt.Println("A:", i)}
}
func b() {for i := 1; i < 1000; i++ {fmt.Println("B:", i)}}func main() {runtime.GOMAXPROCS(1)atime = time.Now().UnixMicro() // 獲取當前時間納秒值go a()go b()runtime.Gosched()btime = time.Now().UnixMicro()fmt.Println(btime - atime)
}

當我們,設置執行該程序的cpu核數為2時。執行時間,大概率是0或者是接近于0的數。

var atime int64
var btime int64func a() {for i := 1; i < 1000; i++ {fmt.Println("A:", i)}
}
func b() {for i := 1; i < 1000; i++ {fmt.Println("B:", i)}}func main() {runtime.GOMAXPROCS(2)atime = time.Now().UnixMicro()go a()go b()runtime.Gosched()btime = time.Now().UnixMicro()fmt.Println("執行完其余協程:", btime-atime)
}

4. channel

單純地將函數并發執行是沒有意義的。函數與函數間需要交換數據才能體現并發執行函數的意義。

雖然可以使用共享內存進行數據交換,但是共享內存在不同的goroutine中容易發生競態問題。為了保證數據交換的正確性,必須使用互斥量對內存進行加鎖,這種做法勢必造成性能問題。

Go語言的并發模型是CSP(Communicating Sequential Processes),提倡通過通信共享內存而不是通過共享內存而實現通信。

如果說goroutine是Go程序并發的執行體,channel就是它們之間的連接。channel是可以讓一個goroutine發送特定值到另一個goroutine的通信機制

Go 語言中的通道(channel)是一種特殊的類型。通道像一個傳送帶或者隊列,總是遵循先入先出(First In First Out)的規則,保證收發數據的順序。每一個通道都是一個具體類型的導管,也就是聲明channel的時候需要為其指定元素類型

  • channel: 在go中,用于協程之間的通信。

  • 共享內存模型: 在共享內存模型中,多個線程可以訪問和修改同一個變量或數據結構。由于線程是并發執行的,它們之間的執行順序是不確定的。如果沒有適當的同步機制來保證原子性和可見性,就有可能導致數據的不一致性和意料之外的結果。

  • CSP模型: CSP 模型通過強調通過通信來共享數據,而不是直接共享內存,可以避免一些線程安全問題。

channel操作?

?通道創建、發送、接受、關閉通道。

// 聲明通道,這時候,不能用的,需要make創建
var ch chan int// 創建指定大小的通道
ints := make(chan int,10)// 發送: 向ch通道發送10
ch <- 10 // 接受:接受來自ch通道的值
x := <-ch// 關閉通道
close(ch)

關于關閉通道需要注意的事情是,只有在通知接收方goroutine所有的數據都發送完畢的時候才需要關閉通道。通道是可以被垃圾回收機制回收的,它和關閉文件是不一樣的,在結束操作之后關閉文件是必須要做的,但關閉通道不是必須的

但是協程之間通道遍歷取值,如果不關閉通道,遍歷的時候會報異常而寄。

    1.對一個關閉的通道再發送值就會導致panic。2.對一個關閉的通道進行接收會一直獲取值直到通道為空。3.對一個關閉的并且沒有值的通道執行接收操作會得到對應類型的零值。4.關閉一個已經關閉的通道會導致panic。

?無緩沖的通道

?無緩沖通道又稱為被阻塞的通道。

func main() {ch := make(chan int)ch <- 10fmt.Println("發送成功")
}

至于為什么執行上面代碼,會報這個錯誤,原因在于,ch := make(chan int),創建是個無緩沖通道。而,

?無緩沖通道的發送操作會被阻塞,直到另一個協程在給通道上執行接受操作,這時值才能發送成功

使用無緩沖通道操作進行通信時將導致發送和接受的協程同步化。(無緩沖通道使用時,有發送,就必須有接受)。因此,無緩沖通道也被稱為同步通道。

這個,無緩沖通道說白了,沒有實際存儲容量,就像快遞員一樣,接受了快遞(數據),必須要送到接受者手里。到最后,不能自己持有。

func main() {ch := make(chan int)go recv(ch)ch <- 10fmt.Println("發送成功")
}func recv(c chan int) {recvData := <-cfmt.Println("接受成功", recvData)
}

有緩沖通道

顧名思義,對比無緩沖通道創建時,是有實際存儲值的。就像快遞柜一樣。快遞員把快遞放到快遞柜里,但是接受者,還沒拿到(但這個過程沒有阻塞)。假如,當有下個快遞來臨時(假如,快遞柜只有1個位置)?。那么,沒有人接受,快遞柜沒位置會再次阻塞。

我們可以通過使用make創建通道時,指定容量,就是有緩沖的通道。

func main() {ch := make(chan int, 1) // 創建一個容量為1的有緩沖區通道ch <- 10fmt.Println("發送成功")
}
func main() {ints := make(chan int, 5)ints <- 1fmt.Println(len(ints), cap(ints)) // 1 5
}

只要通道的容量大于零,那么該通道就是有緩沖的通道,通道的容量表示通道中能存放元素的數量。就像你小區的快遞柜只有那么個多格子,格子滿了就裝不下了,就阻塞了,等到別人取走一個快遞員就能往里面放一個。

我們可以使用內置的len函數獲取通道內元素的數量,使用cap函數獲取通道的容量,雖然我們很少會這么做。

看到這,嗯,channel 專門用于協程之間通信,就是一個臨時的容器,當容器,為0時,你可以往里面輸入元素,但是要接著有輸出;當容量大于0時,你可以往里面存儲東西,這時候不用輸出,但是達到channel 容量頂峰時,你在想存入,必須把之前存入的輸出。

close()

close關閉通道,下次取值變為0,false

func main() {ch := make(chan int)go recv(ch)ch <- 10fmt.Println("發送成功")
}func recv(c chan int) {recvData := <-cfmt.Println("接受成功", recvData)close(c)data, ok := <-cfmt.Println(data, ok)
}

從通道遍歷取值

func main() {var ch1 = make(chan int)go func() {for i := 0; i < 10000; i++ {ch1 <- ifmt.Println("B send", i)}close(ch1)}()for i := range ch1 {fmt.Println("B recv", i)}}

單向通道

func main() {ch1 := make(chan int)go counter(ch1)printer(ch1)
}func counter(out chan<- int) { // 只能發送for i := 0; i < 100; i++ {out <- i * i}close(out)
}func printer(in <-chan int) { // 只能接受for i := range in {fmt.Println(i)}
}

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

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

相關文章

《一生一芯》數字實驗三:加法器與ALU

1. 實驗目標 設計一個能實現如下功能的4位帶符號位的 補碼 ALU&#xff1a; Table 4 ALU 功能列表 ? 功能選擇 功能 操作 000 加法 AB 001 減法 A-B 010 取反 Not A 011 與 A and B 100 或 A or B 101 異或 A xor B 110 比較大小 If A<B then out1…

解讀《網絡安全法》最新修訂,把握網絡安全新趨勢

《網絡安全法》自2017年施行以來&#xff0c;在維護網絡空間安全方面發揮了重要作用。但隨著網絡環境的日益復雜&#xff0c;網絡攻擊、數據泄露等事件頻發&#xff0c;現行法律已難以完全適應新的風險挑戰。 2025年3月28日&#xff0c;國家網信辦會同相關部門起草了《網絡安全…

Java并發編程實戰 Day 10:原子操作類詳解

【Java并發編程實戰 Day 10】原子操作類詳解 開篇 這是“Java并發編程實戰”系列的第10天&#xff0c;我們將深入探討原子操作類的核心技術——CAS原理、ABA問題以及原子類的實現機制。通過理論結合代碼實踐的方式&#xff0c;幫助讀者理解并掌握如何在實際工作中高效使用原子…

瀚文機械鍵盤固件開發詳解:HWKeyboard.h文件解析與應用

【手把手教程】從零開始的機械鍵盤固件開發&#xff1a;HWKeyboard.h詳解 前言 大家好&#xff0c;我是鍵盤DIY愛好者Despacito0o&#xff01;今天想和大家分享我開發的機械鍵盤固件核心頭文件HWKeyboard.h的設計思路和技術要點。這個項目是我多年來對鍵盤固件研究的心血結晶…

2048游戲的技術實現分析-完全Java和Processing版

目錄 簡介Processing庫基礎項目構建指南項目結構核心數據結構游戲核心機制圖形界面實現性能優化代碼詳解設計模式分析測試策略總結與展望簡介 2048是一款由Gabriele Cirulli開發的經典益智游戲。本文將深入分析其Java實現版本的技術細節。該實現使用了Processing庫來創建圖形界…

Spring Boot + Elasticsearch + HBase 構建海量數據搜索系統

Spring Boot Elasticsearch HBase 構建海量數據搜索系統 &#x1f4d6; 目錄 1. 系統需求分析2. 系統架構設計3. Elasticsearch 與 HBase 集成方案4. Spring Boot 項目實現5. 大規模搜索系統最佳實踐 項目概述 本文檔提供了基于 Spring Boot、Elasticsearch 和 HBase 構建…

【iOS】YYModel源碼解析

YYModel源碼解析 文章目錄 YYModel源碼解析前言YYModel性能優勢YYModel簡介YYClassInfo解析YYClassIvarInfo && objc_ivarYYClassMethodInfo && objc_methodYYClassPropertyInfo && property_tYYClassInfo && objc_class YYClassInfo的初始化細…

宇樹科技更名“股份有限公司”深度解析:機器人企業IPO前奏與資本化路徑

從技術落地到資本躍遷&#xff0c;拆解股改背后的上市邏輯與行業啟示 核心事件&#xff1a;股改釋放的上市信號 2025年5月28日&#xff0c;杭州宇樹科技有限公司正式更名“杭州宇樹科技股份有限公司”&#xff0c;市場主體類型變更為“股份有限公司”。盡管官方稱為常規運營調…

Android Native 內存泄漏檢測全解析:從原理到工具的深度實踐

引言 Android應用的內存泄漏不僅發生在Java/Kotlin層&#xff0c;Native&#xff08;C/C&#xff09;層的泄漏同樣普遍且隱蔽。由于Native內存不受Java虛擬機&#xff08;JVM&#xff09;管理&#xff0c;泄漏的內存無法通過GC自動回收&#xff0c;長期積累會導致應用內存占用…

Vortex GPGPU的github流程跑通與功能模塊波形探索(四)

文章目錄 前言一、demo的輸入文件二、trace_csv三、2個值得注意的點3.1 csv指令表格里面的tmask&#xff1f;3.2 rtlsim和simx的log文件&#xff1f; 總結 前言 跟著前面那篇最后留下的幾個問題接著把輸出波形文件和csv文件的輸入、輸出搞明白&#xff01; 一、demo的輸入文件…

UnityPSD文件轉UI插件Psd2UnityuGUIPro3.4.0u2017.4.2介紹:Unity UI設計的高效助手

UnityPSD文件轉UI插件Psd2UnityuGUIPro3.4.0u2017.4.2介紹&#xff1a;Unity UI設計的高效助手 【下載地址】UnityPSD文件轉UI插件Psd2UnityuGUIPro3.4.0u2017.4.2介紹 這款開源插件將PSD文件無縫轉換為Unity的UI元素&#xff0c;極大提升開發效率。它支持一鍵轉換&#xff0c;…

力扣100題之128. 最長連續序列

方法1 使用了hash 方法思路 使用哈希集合&#xff1a;首先將數組中的所有數字存入一個哈希集合中&#xff0c;這樣可以在 O(1) 時間內檢查某個數字是否存在。 尋找連續序列&#xff1a;遍歷數組中的每一個數字&#xff0c;對于每一個數字&#xff0c; 檢查它是否是某個連續序列…

Java爬蟲技術詳解:原理、實現與優勢

一、什么是網絡爬蟲&#xff1f; 網絡爬蟲&#xff08;Web Crawler&#xff09;&#xff0c;又稱網絡蜘蛛或網絡機器人&#xff0c;是一種自動化程序&#xff0c;能夠按照一定的規則自動瀏覽和抓取互聯網上的信息。爬蟲技術是大數據時代獲取網絡數據的重要手段&#xff0c;廣泛…

神經網絡與深度學習 網絡優化與正則化

1.網絡優化存在的難點 &#xff08;1&#xff09;結構差異大&#xff1a;沒有通用的優化算法&#xff1b;超參數多 &#xff08;2&#xff09;非凸優化問題&#xff1a;參數初始化&#xff0c;逃離局部最優 &#xff08;3&#xff09;梯度消失&#xff08;爆炸&#xff09; …

【匯編逆向系列】二、函數調用包含單個參數之整型-ECX寄存器,LEA指令

目錄 一. 匯編源碼 二. 匯編分析 1. ECX寄存器 2. 棧位置計算? 3. 特殊指令深度解析 三、 匯編轉化 一. 匯編源碼 single_int_param:0000000000000040: 89 4C 24 08 mov dword ptr [rsp8],ecx0000000000000044: 57 push rdi0000…

Linux進程替換以及exec六大函數運用

文章目錄 1.進程替換2.替換過程3.替換函數exec3.1命名解釋 4.細說6個exe函數execl函數execvexeclp、execvpexecle、execve 1.進程替換 fork&#xff08;&#xff09;函數在創建子進程后&#xff0c;子進程如果想要執行一個新的程序&#xff0c;就可以使用進程的程序替換來完成…

Selenium操作指南(全)

&#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快 大家好&#xff0c;今天帶大家一起系統的學習下模擬瀏覽器運行庫Selenium&#xff0c;它是一個用于Web自動化測試及爬蟲應用的重要工具。 Selenium測試直接運行在…

結構性設計模式之Facade(外觀)設計模式

結構性設計模式之Facade&#xff08;外觀&#xff09;設計模式 前言&#xff1a; 外觀模式&#xff1a;用自己的話理解就是用戶看到是一個總體頁面&#xff0c;比如xx報名系統頁面。里面有歷年真題模塊、報名模塊、教程模塊、首頁模塊… 做了一個各個模塊的合并&#xff0c;對…

RabbitMQ實用技巧

RabbitMQ是一個流行的開源消息中間件&#xff0c;廣泛用于實現消息傳遞、任務分發和負載均衡。通過合理使用RabbitMQ的功能&#xff0c;可以顯著提升系統的性能、可靠性和可維護性。本文將介紹一些RabbitMQ的實用技巧&#xff0c;包括基礎配置、高級功能及常見問題的解決方案。…

Linux(10)——第二個小程序(自制shell)

目錄 ?編輯 一、引言與動機 &#x1f4dd;背景 &#x1f4dd;主要內容概括 二、全局數據 三、環境變量的初始化 ? 代碼實現 四、構造動態提示符 ? 打印提示符函數 ? 提示符生成函數 ?獲取用戶名函數 ?獲取主機名函數 ?獲取當前目錄名函數 五、命令的讀取與…