編寫過C語言程序的肯定知道通過malloc()方法動態(tài)申請內(nèi)存,其中內(nèi)存分配器使用的是glibc提供的ptmalloc2。 除了glibc,業(yè)界比較出名的內(nèi)存分配器有Google的tcmalloc和Facebook的jemalloc。二者在避免內(nèi)存碎片和性能上均比glic有比較大的優(yōu)勢,在多線程環(huán)境中效果更明顯。

創(chuàng)新互聯(lián)公司專注于杏花嶺企業(yè)網(wǎng)站建設,自適應網(wǎng)站建設,成都做商城網(wǎng)站。杏花嶺網(wǎng)站建設公司,為杏花嶺等地區(qū)提供建站服務。全流程定制網(wǎng)站開發(fā),專業(yè)設計,全程項目跟蹤,創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務
Golang中也實現(xiàn)了內(nèi)存分配器,原理與tcmalloc類似,簡單的說就是維護一塊大的全局內(nèi)存,每個線程(Golang中為P)維護一塊小的私有內(nèi)存,私有內(nèi)存不足再從全局申請。另外,內(nèi)存分配與GC(垃圾回收)關(guān)系密切,所以了解GC前有必要了解內(nèi)存分配的原理。
為了方便自主管理內(nèi)存,做法便是先向系統(tǒng)申請一塊內(nèi)存,然后將內(nèi)存切割成小塊,通過一定的內(nèi)存分配算法管理內(nèi)存。 以64位系統(tǒng)為例,Golang程序啟動時會向系統(tǒng)申請的內(nèi)存如下圖所示:
預申請的內(nèi)存劃分為spans、bitmap、arena三部分。其中arena即為所謂的堆區(qū),應用中需要的內(nèi)存從這里分配。其中spans和bitmap是為了管理arena區(qū)而存在的。
arena的大小為512G,為了方便管理把arena區(qū)域劃分成一個個的page,每個page為8KB,一共有512GB/8KB個頁;
spans區(qū)域存放span的指針,每個指針對應一個page,所以span區(qū)域的大小為(512GB/8KB)乘以指針大小8byte = 512M
bitmap區(qū)域大小也是通過arena計算出來,不過主要用于GC。
span是用于管理arena頁的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),每個span中包含1個或多個連續(xù)頁,為了滿足小對象分配,span中的一頁會劃分更小的粒度,而對于大對象比如超過頁大小,則通過多頁實現(xiàn)。
根據(jù)對象大小,劃分了一系列class,每個class都代表一個固定大小的對象,以及每個span的大小。如下表所示:
上表中每列含義如下:
class: class ID,每個span結(jié)構(gòu)中都有一個class ID, 表示該span可處理的對象類型
bytes/obj:該class代表對象的字節(jié)數(shù)
bytes/span:每個span占用堆的字節(jié)數(shù),也即頁數(shù)乘以頁大小
objects: 每個span可分配的對象個數(shù),也即(bytes/spans)/(bytes/obj)waste
bytes: 每個span產(chǎn)生的內(nèi)存碎片,也即(bytes/spans)%(bytes/obj)上表可見最大的對象是32K大小,超過32K大小的由特殊的class表示,該class ID為0,每個class只包含一個對象。
span是內(nèi)存管理的基本單位,每個span用于管理特定的class對象, 跟據(jù)對象大小,span將一個或多個頁拆分成多個塊進行管理。src/runtime/mheap.go:mspan定義了其數(shù)據(jù)結(jié)構(gòu):
以class 10為例,span和管理的內(nèi)存如下圖所示:
spanclass為10,參照class表可得出npages=1,nelems=56,elemsize為144。其中startAddr是在span初始化時就指定了某個頁的地址。allocBits指向一個位圖,每位代表一個塊是否被分配,本例中有兩個塊已經(jīng)被分配,其allocCount也為2。next和prev用于將多個span鏈接起來,這有利于管理多個span,接下來會進行說明。
有了管理內(nèi)存的基本單位span,還要有個數(shù)據(jù)結(jié)構(gòu)來管理span,這個數(shù)據(jù)結(jié)構(gòu)叫mcentral,各線程需要內(nèi)存時從mcentral管理的span中申請內(nèi)存,為了避免多線程申請內(nèi)存時不斷的加鎖,Golang為每個線程分配了span的緩存,這個緩存即是cache。src/runtime/mcache.go:mcache定義了cache的數(shù)據(jù)結(jié)構(gòu)
alloc為mspan的指針數(shù)組,數(shù)組大小為class總數(shù)的2倍。數(shù)組中每個元素代表了一種class類型的span列表,每種class類型都有兩組span列表,第一組列表中所表示的對象中包含了指針,第二組列表中所表示的對象不含有指針,這么做是為了提高GC掃描性能,對于不包含指針的span列表,沒必要去掃描。根據(jù)對象是否包含指針,將對象分為noscan和scan兩類,其中noscan代表沒有指針,而scan則代表有指針,需要GC進行掃描。mcache和span的對應關(guān)系如下圖所示:
mchache在初始化時是沒有任何span的,在使用過程中會動態(tài)的從central中獲取并緩存下來,跟據(jù)使用情況,每種class的span個數(shù)也不相同。上圖所示,class 0的span數(shù)比class1的要多,說明本線程中分配的小對象要多一些。
cache作為線程的私有資源為單個線程服務,而central則是全局資源,為多個線程服務,當某個線程內(nèi)存不足時會向central申請,當某個線程釋放內(nèi)存時又會回收進central。src/runtime/mcentral.go:mcentral定義了central數(shù)據(jù)結(jié)構(gòu):
lock: 線程間互斥鎖,防止多線程讀寫沖突
spanclass : 每個mcentral管理著一組有相同class的span列表
nonempty: 指還有內(nèi)存可用的span列表
empty: 指沒有內(nèi)存可用的span列表
nmalloc: 指累計分配的對象個數(shù)線程從central獲取span步驟如下:
將span歸還步驟如下:
從mcentral數(shù)據(jù)結(jié)構(gòu)可見,每個mcentral對象只管理特定的class規(guī)格的span。事實上每種class都會對應一個mcentral,這個mcentral的集合存放于mheap數(shù)據(jù)結(jié)構(gòu)中。src/runtime/mheap.go:mheap定義了heap的數(shù)據(jù)結(jié)構(gòu):
lock: 互斥鎖
spans: 指向spans區(qū)域,用于映射span和page的關(guān)系
bitmap:bitmap的起始地址
arena_start: arena區(qū)域首地址
arena_used: 當前arena已使用區(qū)域的最大地址
central: 每種class對應的兩個mcentral
從數(shù)據(jù)結(jié)構(gòu)可見,mheap管理著全部的內(nèi)存,事實上Golang就是通過一個mheap類型的全局變量進行內(nèi)存管理的。mheap內(nèi)存管理示意圖如下:
系統(tǒng)預分配的內(nèi)存分為spans、bitmap、arean三個區(qū)域,通過mheap管理起來。接下來看內(nèi)存分配過程。
針對待分配對象的大小不同有不同的分配邏輯:
(0, 16B) 且不包含指針的對象: Tiny分配
(0, 16B) 包含指針的對象:正常分配
[16B, 32KB] : 正常分配
(32KB, -) : 大對象分配其中Tiny分配和大對象分配都屬于內(nèi)存管理的優(yōu)化范疇,這里暫時僅關(guān)注一般的分配方法。
以申請size為n的內(nèi)存為例,分配步驟如下:
Golang內(nèi)存分配是個相當復雜的過程,其中還摻雜了GC的處理,這里僅僅對其關(guān)鍵數(shù)據(jù)結(jié)構(gòu)進行了說明,了解其原理而又不至于深陷實現(xiàn)細節(jié)。1、Golang程序啟動時申請一大塊內(nèi)存并劃分成spans、bitmap、arena區(qū)域
2、arena區(qū)域按頁劃分成一個個小塊。
3、span管理一個或多個頁。
4、mcentral管理多個span供線程申請使用
5、mcache作為線程私有資源,資源來源于mcentral。
Go 語言是靜態(tài)類型語言,雖然它也可以表現(xiàn)出動態(tài)類型,但是使用一個嵌套的 map[string]interface{} 在那里亂叫會讓代碼變得特別丑。通過掌握語言的靜態(tài)特性,我們可以做的更好。
通過同一通道交換多種信息的時候,我們經(jīng)常需要 JSON 具有動態(tài)的,或者更合適的參數(shù)內(nèi)容。首先,讓我們來討論一下消息封裝(message envelopes),JSON 在這里看起來就像這樣:
通過 interface{},我們可以很容易的將數(shù)據(jù)結(jié)構(gòu)編碼成為獨立封裝的,具有多種類型的消息體的 JSON 數(shù)據(jù)。為了生成下面的 JSON :
我們可以使用這些 Go 類型:
輸出的結(jié)果是:
這些并沒有什么特殊的。
如果你想將上面的 JSON 對象解析成為一個 Envelope 類型的對象,最終你會將 Msg 字段解析成為一個 map[string]interface{}。 這種方式不是很好用,會使你后悔你的選擇。
輸出:
就像前面說的,我推薦修改 Envelope 類型,就像這樣:
json.RawMessage 非常有用,它可以讓你延遲解析相應的 JSON 數(shù)據(jù)。它會將未處理的數(shù)據(jù)存儲為 []byte。
這種方式可以讓你顯式控制 Msg 的解析。從而延遲到獲取到 Type 的值之后,依據(jù) Type 的值進行解析。這種方式不好的地方在于你需要先明確解析 Msg,或者你需要單獨分為 EnvelopeIn 和 EnvelopeOut 兩種類型,其中 EnvelopeOut 仍然有 Msg interface{}。
那么如何將上述兩者好的一面結(jié)合起來呢?通過在 interface{} 字段中放入 *json.RawMessage!
輸出:
雖然我極其推薦你將動態(tài)可變的部分放在一個單獨的 key 下面,但是有時你可能需要處理一些預先存在的數(shù)據(jù),它們并沒有用這樣的方式進行格式化。
如果可以的話,請使用文章前面提到的風格。
我們可以通過解析兩次數(shù)據(jù)的方式來解決。
dynamite
1、服務器編程:以前你如果使用C或者C++做的那些事情,用Go來做很合適,例如處理日志、數(shù)據(jù)打包、虛擬機處理、文件系統(tǒng)等。
2、分布式系統(tǒng)、數(shù)據(jù)庫代理器、中間件:例如Etcd。
3、網(wǎng)絡編程:這一塊目前應用最廣,包括Web應用、API應用、下載應用,而且Go內(nèi)置的net/http包基本上把我們平常用到的網(wǎng)絡功能都實現(xiàn)了。
4、開發(fā)云平臺:目前國外很多云平臺在采用Go開發(fā),我們所熟知的七牛云、華為云等等都有使用Go進行開發(fā)并且開源的成型的產(chǎn)品。
5、區(qū)塊鏈:目前有一種說法,技術(shù)從業(yè)人員把Go語言稱作為區(qū)塊鏈行業(yè)的開發(fā)語言。如果大家學習區(qū)塊鏈技術(shù)的話,就會發(fā)現(xiàn)現(xiàn)在有很多很多的區(qū)塊鏈的系統(tǒng)和應用都是采用Go進行開發(fā)的,比如ehtereum是目前知名度最大的公鏈,再比如fabric是目前最知名的聯(lián)盟鏈,兩者都有g(shù)o語言的版本,且go-ehtereum還是以太坊官方推薦的版本。
自1.0版發(fā)布以來,go語言引起了眾多開發(fā)者的關(guān)注,并得到了廣泛的應用。go語言簡單、高效、并發(fā)的特點吸引了許多傳統(tǒng)的語言開發(fā)人員,其數(shù)量也在不斷增加。
使用 Go 語言開發(fā)的開源項目非常多。早期的 Go 語言開源項目只是通過 Go 語言與傳統(tǒng)項目進行C語言庫綁定實現(xiàn),例如 Qt、Sqlite 等。
后期的很多項目都使用 Go 語言進行重新原生實現(xiàn),這個過程相對于其他語言要簡單一些,這也促成了大量使用 Go 語言原生開發(fā)項目的出現(xiàn)。
三次握手:
1. 主動發(fā)起連接請求端(客戶端),發(fā)送 SYN 標志位,攜帶數(shù)據(jù)包、包號
2. 被動接收連接請求端(服務器),接收 SYN,回復 ACK,攜帶應答序列號。同時,發(fā)送SYN標志位,攜帶數(shù)據(jù)包、包號
3. 主動發(fā)起連接請求端(客戶端),接收SYN 標志位,回復 ACK。
被動端(服務器)接收 ACK —— 標志著 三次握手建立完成( Accept()/Dial() 返回 )
四次揮手:
1. 主動請求斷開連接端(客戶端), 發(fā)送 FIN標志,攜帶數(shù)據(jù)包
2. 被動接受斷開連接端(服務器), 發(fā)送 ACK標志,攜帶應答序列號。 —— 半關(guān)閉完成。
3. 被動接受斷開連接端(服務器), 發(fā)送 FIN標志,攜帶數(shù)據(jù)包
4. 主動請求斷開連接端(客戶端), 發(fā)送 最后一個 ACK標志,攜帶應答序列號。—— 發(fā)送完成,客戶端不會直接退出,等 2MSL時長。
等 2MSL待目的:確保服務器 收到最后一個ACK
滑動窗口:
通知對端本地存儲數(shù)據(jù)的 緩沖區(qū)容量。—— write 函數(shù)在對端 緩沖區(qū)滿時,有可能阻塞。
TCP狀態(tài)轉(zhuǎn)換:
1. 主動發(fā)起連接請求端:
CLOSED —— 發(fā)送SYN —— SYN_SENT(了解) —— 接收ACK、SYN,回發(fā) ACK —— ESTABLISHED (數(shù)據(jù)通信)
2. 主動關(guān)閉連接請求端:
ESTABLISHED —— 發(fā)送FIN —— FIN_WAIT_1 —— 接收ACK —— FIN_WAIT_2 (半關(guān)閉、主動端)
—— 接收FIN、回復ACK —— TIME_WAIT (主動端) —— 等 2MSL 時長 —— CLOSED
3. 被動建立連接請求端:
CLOSED —— LISTEN —— 接收SYN、發(fā)送ACK、SYN —— SYN_RCVD —— 接收 ACK —— ESTABLISHED (數(shù)據(jù)通信)
4. 被動斷開連接請求端:
ESTABLISHED —— 接收 FIN、發(fā)送 ACK —— CLOSE_WAIT —— 發(fā)送 FIN —— LAST_ACK —— 接收ACK —— CLOSED
windows下查看TCP狀態(tài)轉(zhuǎn)換:
netstat -an | findstr? 端口號
Linux下查看TCP狀態(tài)轉(zhuǎn)換:
netstat -an | grep? 端口號
TCP和UDP對比:?
TCP: 面向連接的可靠的數(shù)據(jù)包傳遞。 針對不穩(wěn)定的 網(wǎng)絡層,完全彌補。ACK
UDP:無連接不可靠的報文傳輸。 針對不穩(wěn)定的 網(wǎng)絡層,完全不彌補。還原網(wǎng)絡真實狀態(tài)。
優(yōu)點???????????????????????????????????????????????????????????? 缺點
TCP: 可靠、順序、穩(wěn)定 ???????????????????????????????????? 系統(tǒng)資源消耗大,程序?qū)崿F(xiàn)繁復、速度慢
UDP:系統(tǒng)資源消耗小,程序?qū)崿F(xiàn)簡單、速度快 ???????????????????????? 不可靠、無序、不穩(wěn)定
使用場景:
TCP:大文件、可靠數(shù)據(jù)傳輸。 對數(shù)據(jù)的 穩(wěn)定性、準確性、一致性要求較高的場合。
UDP:應用于對數(shù)據(jù)時效性要求較高的場合。 網(wǎng)絡直播、電話會議、視頻直播、網(wǎng)絡游戲。
UDP-CS-Server實現(xiàn)流程:
1.? 創(chuàng)建 udp地址結(jié)構(gòu) ResolveUDPAddr(“協(xié)議”, “IP:port”) —— udpAddr 本質(zhì) struct{IP、port}
2.? 創(chuàng)建用于 數(shù)據(jù)通信的 socket ListenUDP(“協(xié)議”, udpAddr ) —— udpConn (socket)
3.? 從客戶端讀取數(shù)據(jù),獲取對端的地址 udpConn.ReadFromUDP() —— 返回:n,clientAddr, err
4.? 發(fā)送數(shù)據(jù)包給 客戶端 udpConn.WriteToUDP("數(shù)據(jù)", clientAddr)
UDP-CS-Client實現(xiàn)流程:
1.? 創(chuàng)建用于通信的 socket。 net.Dial("udp", "服務器IP:port") —— udpConn (socket)
2.? 以后流程參見 TCP客戶端實現(xiàn)源碼。
UDPserver默認就支持并發(fā)!
------------------------------------
命令行參數(shù): 在main函數(shù)啟動時,向整個程序傳參。 【重點】
語法: go run xxx.go ? argv1 argv2? argv3? argv4 。。。
xxx.exe:? 第 0 個參數(shù)。
argv1 :第 1 個參數(shù)。
argv2 :第 2 個參數(shù)。
argv3 :第 3 個參數(shù)。
argv4 :第 4 個參數(shù)。
使用: list := os.Args? 提取所有命令行參數(shù)。
獲取文件屬性函數(shù):
os.stat(文件訪問絕對路徑) —— fileInfo 接口
fileInfo 包含 兩個接口。
Name() 獲取文件名。 不帶訪問路徑
Size() 獲取文件大小。
網(wǎng)絡文件傳輸 —— 發(fā)送端(客戶端)
1.? 獲取命令行參數(shù),得到文件名(帶路徑)filePath list := os.Args
2.? 使用 os.stat() 獲取 文件名(不帶路徑)fileName
3.? 創(chuàng)建 用于數(shù)據(jù)傳輸?shù)?socket? net.Dial("tcp", “服務器IP+port”) —— conn
4.? 發(fā)送文件名(不帶路徑)? 給接收端, conn.write()
5.? 讀取 接收端回發(fā)“ok”,判斷無誤。封裝函數(shù) sendFile(filePath, conn) 發(fā)送文件內(nèi)容
6.? 實現(xiàn) sendFile(filePath,? conn)
1) 只讀打開文件 os.Open(filePath)
for {
2) 從文件中讀數(shù)據(jù)? f.Read(buf)
3) 將讀到的數(shù)據(jù)寫到socket中? conn.write(buf[:n])
4)判斷讀取文件的 結(jié)尾。 io.EOF. 跳出循環(huán)
}
網(wǎng)絡文件傳輸 —— 接收端(服務器)
1. 創(chuàng)建用于監(jiān)聽的 socket net.Listen() —— listener
2. 借助listener 創(chuàng)建用于 通信的 socket listener.Accpet()? —— conn
3. 讀取 conn.read() 發(fā)送端的 文件名, 保存至本地。
4. 回發(fā) “ok”應答 發(fā)送端。
5. 封裝函數(shù),接收文件內(nèi)容 recvFile(文件路徑)
1) f = os.Create(帶有路徑的文件名)
for {
2)從 socket中讀取發(fā)送端發(fā)送的 文件內(nèi)容 。 conn.read(buf)
3)? 將讀到的數(shù)據(jù) 保存至本地文件 f.Write(buf[:n])
4)? 判斷 讀取conn 結(jié)束, 代表文件傳輸完成。 n == 0? break
}
當前題目:go語言動態(tài)圖 go語言是動態(tài)語言嗎
URL網(wǎng)址:http://chinadenli.net/article40/hpjgho.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供做網(wǎng)站、自適應網(wǎng)站、建站公司、品牌網(wǎng)站制作、云服務器、關(guān)鍵詞優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)