" />

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

GoLang并發(fā)機制探究goroutine原理詳細(xì)講解

 更新時間:2022年12月15日 10:06:14   作者:cactusblossom  
goroutine是Go語言提供的語言級別的輕量級線程,在我們需要使用并發(fā)時,我們只需要通過 go 關(guān)鍵字來開啟 goroutine 即可。這篇文章主要介紹了GoLang并發(fā)機制goroutine原理,感興趣的可以了解一下

通常程序會被編寫為一個順序執(zhí)行并完成一個獨立任務(wù)的代碼。如果沒有特別的需求,最好總是這樣寫代碼,因為這種類型的程序通常很容易寫,也很容易維護。不過也有一些情況下,并行執(zhí)行多個任務(wù)會有更大的好處。一個例子是,Web 服務(wù)需要在各自獨立的套接字(socket)上同時接收多個數(shù)據(jù)請求。每個套接字請求都是獨立的,可以完全獨立于其他套接字進行處理。具有并行執(zhí)行多個請求的能力可以顯著提高這類系統(tǒng)的性能。考慮到這一點,Go 語言的語法和運行時直接內(nèi)置了對并發(fā)的支持。

1. 進程與線程

當(dāng)運行一個應(yīng)用程序的時候,操作系統(tǒng)會為這個應(yīng)用程序啟動一個進程??梢詫⑦@個進程看作一個包含了應(yīng)用程序在運行中需要用到和維護的各種資源的容器。這些資源包括但不限于內(nèi)存地址空間、文件和設(shè)備的句柄以及線程。

一個線程是一個執(zhí)行空間,這個空間會被 操作系統(tǒng)調(diào)度來運行函數(shù)中所寫的代碼。每個進程至少包含一個線程,每個進程的初始線程被稱作主線程。因為執(zhí)行這個線程的空間是應(yīng)用程序的本身的空間,所以當(dāng)主線程終止時,應(yīng)用程序也會終止。操作系統(tǒng)將線程調(diào)度到某個處理器上運行,這個處理器并不一定是進程所在的處理器。不同操作系統(tǒng)使用的線程調(diào)度算法一般都不一樣,但是這種不同會被 操作系統(tǒng)屏蔽,并不會展示給程序員。

2. goroutine原理

Go 語言里的并發(fā)指的是能讓某個函數(shù)獨立于其他函數(shù)運行的能力。操作系統(tǒng)會在物理處理器上調(diào)度線程來運行,而Go語言中當(dāng)一個函數(shù)創(chuàng)建為goroutine時,Go 會將其視為一個獨立的工作單元,這個單元會被調(diào)度到可用的邏輯處理器上執(zhí)行。每個邏輯處理器都分別綁定到單個操作系統(tǒng)線程。Go語言運行時默認(rèn)會為每個可用的物理處理器分配一個邏輯處理器。

Go 語言運行時的調(diào)度器是一個復(fù)雜的軟件,能管理被創(chuàng)建的所有g(shù)oroutine 并為其分配執(zhí)行時間。這個調(diào)度器在操作系統(tǒng)之上,將操作系統(tǒng)的線程與運行時的邏輯處理器綁定,并在邏輯處理器上運行g(shù)oroutine。調(diào)度器在任何給定的時間,都會全面控制哪個goroutine 要在哪個邏輯處理器上運行。

下圖中可以看到操作系統(tǒng)線程、邏輯處理器和本地運行隊列之間的關(guān)系。如果創(chuàng)建一個goroutine 并準(zhǔn)備運行,這個goroutine 就會被放到調(diào)度器的全局運行隊列中。之后,調(diào)度器就將這些隊列中的goroutine 分配給一個邏輯處理器,并放到這個邏輯處理器對應(yīng)的本地運行隊列中。本地運行隊列中的goroutine 會一直等待直到自己被分配的邏輯處理器執(zhí)行。

有時,正在運行的goroutine 需要執(zhí)行一個阻塞的系統(tǒng)調(diào)用,如打開一個文件。當(dāng)這類調(diào)用發(fā)生時,線程和goroutine 會從邏輯處理器上分離,該線程會繼續(xù)阻塞,等待系統(tǒng)調(diào)用的返回。與此同時,這個邏輯處理器就失去了用來運行的線程。所以,調(diào)度器會創(chuàng)建一個新線程,并將其綁定到該邏輯處理器上。之后,邏輯處理器會從本地運行隊列里選擇另一個goroutine 來運行。一旦被阻塞的系統(tǒng)調(diào)用執(zhí)行完成并返回,對應(yīng)的goroutine 會放回到本地運行隊列,而之前的線程會保存好,以便之后可以繼續(xù)使用。

如果一個 goroutine 需要做一個網(wǎng)絡(luò)I/O 調(diào)用,流程上會有些不一樣。在這種情況下,goroutine會和邏輯處理器分離,并移到集成了網(wǎng)絡(luò)輪詢器的運行時。一旦該輪詢器指示某個網(wǎng)絡(luò)讀或者寫操作已經(jīng)就緒,對應(yīng)的goroutine 就會重新分配到邏輯處理器上來完成操作。調(diào)度器對可以創(chuàng)建的邏輯處理器的數(shù)量沒有限制,但語言運行時默認(rèn)限制每個程序最多創(chuàng)建10 000 個線程。這個限制值可以通過調(diào)用runtime/debug 包的SetMaxThreads 方法來更改。如果程序試圖使用更多的線程,就會崩潰。

3. 并發(fā)與并行

并發(fā)(concurrency)不是并行(parallelism)。并行是讓不同的代碼片段同時在不同的物理處理器上執(zhí)行。并行的關(guān)鍵是同時做很多事情,而并發(fā)是指同時管理很多事情,這些事情可能只做了一半就被暫停去做別的事情了。在很多情況下,并發(fā)的效果比并行好,因為操作系統(tǒng)和硬件的總資源一般很少,但能支持系統(tǒng)同時做很多事情。這種“使用較少的資源做更多的事情”的哲學(xué),也是指導(dǎo)Go 語言設(shè)計的哲學(xué)。

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

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

3.1 在1個邏輯處理器上運行Go程序

下面的代碼通過調(diào)用runtime 包的GOMAXPROCS 函數(shù),更改調(diào)度器只可以使用1個邏輯處理器。創(chuàng)建兩個goroutine,以并發(fā)的形式分別顯示大寫和小寫的英文字母:

package main
import (
	"fmt"
	"runtime"
	"sync"
)
func main() {
	runtime.GOMAXPROCS(1) // 分配一個邏輯處理器給調(diào)度器使用
	var wg sync.WaitGroup
	wg.Add(2)
	fmt.Println("Start Goroutines")
	go func() {
		defer wg.Done()
		for count := 0; count < 3; count++ {
			for char := 'a'; char < 'a'+26; char++ {
				fmt.Printf("%c ", char)
			}
		}
	}()
	go func() {
		defer wg.Done()
		for count := 0; count < 3; count++ {
			for char := 'A'; char < 'A'+26; char++ {
				fmt.Printf("%c ", char)
			}
		}
	}()
	fmt.Println("Waiting To Finish")
	wg.Wait()
	fmt.Println("\nTerminating Program")
}

程序的輸出為:

Start Goroutines
Waiting To Finish
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n
o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z 
Terminating Program

使用1個邏輯處理器,在同一個時刻實際只有一個線程在運行,而且每個goroutine花費的時間太短,并沒有發(fā)生goroutine的停止與重新調(diào)度,所以通過程序輸出可以看出每個goroutine在一個邏輯處理器上并發(fā)運行的效果,他們看起來是順序執(zhí)行的。

3.2 goroutine的停止與重新調(diào)度

基于調(diào)度器的內(nèi)部算法,一個正運行的goroutine 在工作結(jié)束前,可以被停止并重新調(diào)度。調(diào)度器這樣做的目的是防止某個goroutine 長時間占用邏輯處理器。當(dāng)goroutine 占用時間過長時,調(diào)度器會停止當(dāng)前正運行的goroutine,并給其他可運行的goroutine 運行的機會。

下圖從邏輯處理器的角度展示了這一場景。在第1 步,調(diào)度器開始運行g(shù)oroutine A,而goroutine B 在運行隊列里等待調(diào)度。之后,在第2 步,調(diào)度器交換了goroutine A 和goroutine B。由于goroutine A 并沒有完成工作,因此被放回到運行隊列。之后,在第3 步,goroutine B 完成了它的工作并被系統(tǒng)銷毀。這也讓goroutine A 繼續(xù)之前的工作。

下面的代碼中,同樣設(shè)置只使用1個邏輯處理器,程序創(chuàng)建了兩個goroutine,分別打印1~5000 內(nèi)的素數(shù)。查找并顯示素數(shù)會消耗不少時間,這會讓調(diào)度器有機會在第一個goroutine 找到所有素數(shù)之前,切換該goroutine的時間片:

package main
import (
	"fmt"
	"runtime"
	"sync"
)
var wg sync.WaitGroup
func main() {
	runtime.GOMAXPROCS(1) // 分配一個邏輯處理器給調(diào)度器使用
	wg.Add(2)
	// 創(chuàng)建兩個goroutine
	fmt.Println("Create Goroutines")
	go printPrime("A")
	go printPrime("B")
	fmt.Println("Waiting To Finish")
	wg.Wait()
	fmt.Println("Terminating Program")
}
// 顯示 5000 以內(nèi)的素數(shù)值
func printPrime(prefix string) {
	defer wg.Done()
next:
	for outer := 2; outer < 5000; outer++ {
		for inner := 2; inner < outer; inner++ {
			if outer%inner == 0 {
				continue next
			}
		}
		fmt.Printf("%s:%d\n", prefix, outer)
	}
	fmt.Println("Completed", prefix)
}

程序的輸出為:

Create Goroutines
Waiting To Finish
B:2
B:3
...
B:4583
B:4591
A:3 ** 切換 goroutine
A:5
...
A:4561
A:4567
B:4603 ** 切換 goroutine
B:4621
...
Completed B
A:4457 ** 切換 goroutine
A:4463
...
A:4993
A:4999
Completed A
Terminating Program

goroutine B 先顯示素數(shù)。goroutine B 打印到素數(shù)4591后,調(diào)度器就將正運行的goroutine切換為goroutine A。之后goroutine A 在線程上執(zhí)行了一段時間,再次切換為goroutine B。這次goroutine B 完成了所有的工作。一旦goroutine B 返回,就會看到線程再次切換到goroutine A 并完成所有的工作。每次運行這個程序,調(diào)度器切換的時間點都會稍微有些不同。

3.3 在多個邏輯處理器上運行Go程序

如果給調(diào)度器分配多個邏輯處理器,我們會看到之前的示例程序的輸出行為會有些不同。下面的代碼中把邏輯處理器的數(shù)量改為2,讓我們看看打印英文字母的效果:

package main
import (
	"fmt"
	"runtime"
	"sync"
)
func main() {
	runtime.GOMAXPROCS(2) // 分配2個邏輯處理器給調(diào)度器使用
	var wg sync.WaitGroup
	wg.Add(2)
	fmt.Println("Start Goroutines")
	go func() {
		defer wg.Done()
		// 顯示小寫字母表3 次
		for count := 0; count < 3; count++ {
			for char := 'a'; char < 'a'+26; char++ {
				fmt.Printf("%c ", char)
			}
		}
	}()
	go func() {
		defer wg.Done()
		// 顯示大寫字母表3 次
		for count := 0; count < 3; count++ {
			for char := 'A'; char < 'A'+26; char++ {
				fmt.Printf("%c ", char)
			}
		}
	}()
	fmt.Println("Waiting To Finish")
	wg.Wait()
	fmt.Println("\nTerminating Program")
}

程序輸出為:

Start Goroutines
Waiting To Finish
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S a b c d e f g h i j k l m 
n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z T U 
V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 
Terminating Program

兩個goroutine 幾乎是同時開始運行的,大小寫字母是混合在一起顯示的。所以每個goroutine 獨自運行在自己的線程上。

到此這篇關(guān)于GoLang并發(fā)機制探究goroutine原理詳細(xì)講解的文章就介紹到這了,更多相關(guān)GoLang goroutine內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言題解LeetCode599兩個列表的最小索引總和

    Go語言題解LeetCode599兩個列表的最小索引總和

    這篇文章主要為大家介紹了Go語言題解LeetCode599兩個列表的最小索引總和示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • 詳解Golang如何比較兩個slice是否相等

    詳解Golang如何比較兩個slice是否相等

    開發(fā)中常會遇到需要比較兩個slice包含的元素是否完全相等的情況,我們通常會通過兩種方法去比較切片是否相等。這里通過幾個示例來看一下這兩種方法,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助
    2022-11-11
  • 詳解Golang中Requests包的使用

    詳解Golang中Requests包的使用

    Go的net/http包雖然功能強大、用途也廣告,但要想正確的使用請求的客戶端是非常繁瑣的,所以本文和大家分享一個高效的HTTP的請求包carlmjohnson/requests的使用,需要的小伙伴可以了解一下
    2023-06-06
  • Go語言七篇入門教程二程序結(jié)構(gòu)與數(shù)據(jù)類型

    Go語言七篇入門教程二程序結(jié)構(gòu)與數(shù)據(jù)類型

    這篇文章主要為大家介紹了Go語言的程序結(jié)構(gòu)與數(shù)據(jù)類型,本篇文章是Go語言七篇入門系列文,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-11-11
  • Golang二維數(shù)組的使用方式

    Golang二維數(shù)組的使用方式

    之前給大家講過很多二維數(shù)組的知識,今天重點給大家介紹Golang二維數(shù)組的使用方式,通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-05-05
  • golang定時器Timer的用法和實現(xiàn)原理解析

    golang定時器Timer的用法和實現(xiàn)原理解析

    這篇文章主要介紹了golang定時器Ticker,本文主要來看一下Timer的用法和實現(xiàn)原理,需要的朋友可以參考以下內(nèi)容
    2023-04-04
  • 淺析Go匯編語法和MatrixOne使用介紹

    淺析Go匯編語法和MatrixOne使用介紹

    MatrixOne由Go語言所開發(fā)是一個新一代超融合異構(gòu)數(shù)據(jù)庫,致力于打造單一架構(gòu)處理TP、AP、流計算等多種負(fù)載的極簡大數(shù)據(jù)引擎,今天通過本文給大家介紹Go匯編語法和MatrixOne使用,感興趣的朋友一起看看吧
    2022-04-04
  • Golang Goroutine的使用

    Golang Goroutine的使用

    這篇文章主要介紹了Golang Goroutine的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • go語言實現(xiàn)sqrt的方法

    go語言實現(xiàn)sqrt的方法

    這篇文章主要介紹了go語言實現(xiàn)sqrt的方法,實例分析了Go語言實現(xiàn)計算平方根的技巧,需要的朋友可以參考下
    2015-03-03
  • golang新手們?nèi)菀追傅?個錯誤總結(jié)

    golang新手們?nèi)菀追傅?個錯誤總結(jié)

    這篇文章主要給大家介紹了關(guān)于golang新手們?nèi)菀追傅?個錯誤,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08

最新評論