本文目錄一覽:
- 1、java的多線程在golang中是如何體現的?
- 2、golang多線程簡單邏輯
- 3、(十一)golang 內存分析
- 4、Golang什麼時候會觸發GC
- 5、golang 每一條請求都是一個新的線程嗎
- 6、golang web 瀏覽器每請求一次 golang是啟動一個新的線程嗎
java的多線程在golang中是如何體現的?
golang語言中實現Java的多線程主要是使用LockOSThread() 方法,代碼如下:
package mainimport (
“log”
“runtime”
“time”)func main() {
runtime.GOMAXPROCS(1)
for i := 0; i 10; i++ {
go func() {
//runtime.LockOSThread()
for {
log.Println(“a”)
time.Sleep(time.Second)
}
}()
}
time.Sleep(time.Hour)}
運行之後,就會產生10個進程,如下截圖:
golang多線程簡單邏輯
實現指定個核心最大化使用,比如核心總數減一。
必要的庫。
要使用的cpu數量,建議不全使用。
建立管道。
聲明使用的cpu數。
建立互斥關係,本例中主要為了實現所有線程執行完後再執行後續程序。
創建cpu數減1個線程
後面每個任務結束時要done一個wg,這裡根據具體情況加,是循環就在每個循環里加,保證後面能全部done即可
沒有緩衝的、阻塞式的往管道傳遞字元串。
Wait是等所有線程都執行完,即增加的數字被全done掉。
關閉管道。
假設已有的函數是ReadLogs,在它的基礎上加個Wg加函數名的新函數,我覺得這種方式不改變原有的,比較舒服。
大意是:循環從管道讀取字元串,讀不到了就跳出循環。
每個ReadLogs()之後加一個wg.Done(),相當於計數減一。
ReadLogs()就是要執行的任務,不再解釋。
就是開指定個線程。
管道阻塞傳值。
wg同步。
WgReadLogs循環接收。
(十一)golang 內存分析
編寫過C語言程序的肯定知道通過malloc()方法動態申請內存,其中內存分配器使用的是glibc提供的ptmalloc2。 除了glibc,業界比較出名的內存分配器有Google的tcmalloc和Facebook的jemalloc。二者在避免內存碎片和性能上均比glic有比較大的優勢,在多線程環境中效果更明顯。
Golang中也實現了內存分配器,原理與tcmalloc類似,簡單的說就是維護一塊大的全局內存,每個線程(Golang中為P)維護一塊小的私有內存,私有內存不足再從全局申請。另外,內存分配與GC(垃圾回收)關係密切,所以了解GC前有必要了解內存分配的原理。
為了方便自主管理內存,做法便是先向系統申請一塊內存,然後將內存切割成小塊,通過一定的內存分配演算法管理內存。 以64位系統為例,Golang程序啟動時會向系統申請的內存如下圖所示:
預申請的內存劃分為spans、bitmap、arena三部分。其中arena即為所謂的堆區,應用中需要的內存從這裡分配。其中spans和bitmap是為了管理arena區而存在的。
arena的大小為512G,為了方便管理把arena區域劃分成一個個的page,每個page為8KB,一共有512GB/8KB個頁;
spans區域存放span的指針,每個指針對應一個page,所以span區域的大小為(512GB/8KB)乘以指針大小8byte = 512M
bitmap區域大小也是通過arena計算出來,不過主要用於GC。
span是用於管理arena頁的關鍵數據結構,每個span中包含1個或多個連續頁,為了滿足小對象分配,span中的一頁會劃分更小的粒度,而對於大對象比如超過頁大小,則通過多頁實現。
根據對象大小,劃分了一系列class,每個class都代表一個固定大小的對象,以及每個span的大小。如下表所示:
上表中每列含義如下:
class: class ID,每個span結構中都有一個class ID, 表示該span可處理的對象類型
bytes/obj:該class代表對象的位元組數
bytes/span:每個span佔用堆的位元組數,也即頁數乘以頁大小
objects: 每個span可分配的對象個數,也即(bytes/spans)/(bytes/obj)waste
bytes: 每個span產生的內存碎片,也即(bytes/spans)%(bytes/obj)上表可見最大的對象是32K大小,超過32K大小的由特殊的class表示,該class ID為0,每個class只包含一個對象。
span是內存管理的基本單位,每個span用於管理特定的class對象, 跟據對象大小,span將一個或多個頁拆分成多個塊進行管理。src/runtime/mheap.go:mspan定義了其數據結構:
以class 10為例,span和管理的內存如下圖所示:
spanclass為10,參照class表可得出npages=1,nelems=56,elemsize為144。其中startAddr是在span初始化時就指定了某個頁的地址。allocBits指向一個點陣圖,每位代表一個塊是否被分配,本例中有兩個塊已經被分配,其allocCount也為2。next和prev用於將多個span鏈接起來,這有利於管理多個span,接下來會進行說明。
有了管理內存的基本單位span,還要有個數據結構來管理span,這個數據結構叫mcentral,各線程需要內存時從mcentral管理的span中申請內存,為了避免多線程申請內存時不斷的加鎖,Golang為每個線程分配了span的緩存,這個緩存即是cache。src/runtime/mcache.go:mcache定義了cache的數據結構
alloc為mspan的指針數組,數組大小為class總數的2倍。數組中每個元素代表了一種class類型的span列表,每種class類型都有兩組span列表,第一組列表中所表示的對象中包含了指針,第二組列表中所表示的對象不含有指針,這麼做是為了提高GC掃描性能,對於不包含指針的span列表,沒必要去掃描。根據對象是否包含指針,將對象分為noscan和scan兩類,其中noscan代表沒有指針,而scan則代表有指針,需要GC進行掃描。mcache和span的對應關係如下圖所示:
mchache在初始化時是沒有任何span的,在使用過程中會動態的從central中獲取並緩存下來,跟據使用情況,每種class的span個數也不相同。上圖所示,class 0的span數比class1的要多,說明本線程中分配的小對象要多一些。
cache作為線程的私有資源為單個線程服務,而central則是全局資源,為多個線程服務,當某個線程內存不足時會向central申請,當某個線程釋放內存時又會回收進central。src/runtime/mcentral.go:mcentral定義了central數據結構:
lock: 線程間互斥鎖,防止多線程讀寫衝突
spanclass : 每個mcentral管理著一組有相同class的span列表
nonempty: 指還有內存可用的span列表
empty: 指沒有內存可用的span列表
nmalloc: 指累計分配的對象個數線程從central獲取span步驟如下:
將span歸還步驟如下:
從mcentral數據結構可見,每個mcentral對象只管理特定的class規格的span。事實上每種class都會對應一個mcentral,這個mcentral的集合存放於mheap數據結構中。src/runtime/mheap.go:mheap定義了heap的數據結構:
lock: 互斥鎖
spans: 指向spans區域,用於映射span和page的關係
bitmap:bitmap的起始地址
arena_start: arena區域首地址
arena_used: 當前arena已使用區域的最大地址
central: 每種class對應的兩個mcentral
從數據結構可見,mheap管理著全部的內存,事實上Golang就是通過一個mheap類型的全局變數進行內存管理的。mheap內存管理示意圖如下:
系統預分配的內存分為spans、bitmap、arean三個區域,通過mheap管理起來。接下來看內存分配過程。
針對待分配對象的大小不同有不同的分配邏輯:
(0, 16B) 且不包含指針的對象: Tiny分配
(0, 16B) 包含指針的對象:正常分配
[16B, 32KB] : 正常分配
(32KB, -) : 大對象分配其中Tiny分配和大對象分配都屬於內存管理的優化範疇,這裡暫時僅關注一般的分配方法。
以申請size為n的內存為例,分配步驟如下:
Golang內存分配是個相當複雜的過程,其中還摻雜了GC的處理,這裡僅僅對其關鍵數據結構進行了說明,了解其原理而又不至於深陷實現細節。1、Golang程序啟動時申請一大塊內存並劃分成spans、bitmap、arena區域
2、arena區域按頁劃分成一個個小塊。
3、span管理一個或多個頁。
4、mcentral管理多個span供線程申請使用
5、mcache作為線程私有資源,資源來源於mcentral。
Golang什麼時候會觸發GC
Golang採用了三色標記法來進行垃圾回收,那麼在什麼場景下會觸發這個回收動作呢?
源碼主要位於文件 src/runtime/mgc.go go version 1.16
觸發條件從大方面說,可分為 手動觸發 和 系統觸發 兩種方式。手動觸發一般很少用,主要由開發者通過調用 runtime.GC() 函數來實現,而對於系統自動觸發是 運行時 根據一些條件判斷來進行的,這也正是本文要介紹的內容。
不管哪種觸發方式,底層回收機制是一樣的,所以我們先看一下手動觸發,根據它來找系統觸發的條件。
可以看到開始執行GC的是 gcStart() 函數,它有一個 gcTrigger 參數,是一個觸發條件結構體,它的結構體也很簡單。
其實在Golang 內部所有的GC都是通過 gcStart() 函數,然後指定一個 gcTrigger 的參數來開始的,而手動觸髮指定的條件值為 gcTriggerCycle 。 gcStart 是一個很複雜的函數,有興趣的可以看一下源碼實現。
對於 kind 的值有三種,分別為 gcTriggerHeap 、 gcTriggerTime 和 gcTriggerCycle 。
運行時會通過 gcTrigger.test() 函數來決定是否需要觸發GC,只要滿足上面基中一個即可。
到此我們基本明白了這三種觸發GC的條件,那麼對於系統自動觸發這種,Golang 從一個程序的開始到運行,它又是如何一步一步監控到這個條件的呢?
其實 runtime 在程序啟動時,會在一個初始化函數 init() 里啟用一個 forcegchelper() 函數,這個函數位於 proc.go 文件。
為了減少系統資源佔用,在 forcegchelper 函數里會通過 goparkunlock() 函數主動讓自己陷入休眠,以後由 sysmon() 監控線程根據條件來恢復這個gc goroutine。
可以看到 sysmon() 會在一個 for 語句里一直判斷這個 gcTriggerTime 這個條件是否滿足,如果滿足的話,會將 forcegc.g 這個 goroutine 添加到全局隊列里進行調度(這裡 forcegc 是一個全局變數)。
調度器在調度循環 runtime.schedule 中還可以通過垃圾收集控制器的 runtime.gcControllerState.findRunnabledGCWorker 獲取並執行用於後台標記的任務。
golang 每一條請求都是一個新的線程嗎
、申明一個channel,並發產生的業務放入channel中,啟動一個或者多個goroutin讀取channel中的數據並處理。
golang web 瀏覽器每請求一次 golang是啟動一個新的線程嗎
是協程,每一個請求都會單獨啟動一個 goroutine,可以理解為輕量級線程
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/279014.html