本文目錄一覽:
- 1、golang 怎麼定義可變參數的函數
- 2、Golang 遊戲leaf系列(六) Go模塊
- 3、基礎知識 – Golang 中的格式化輸入輸出
- 4、golang原生數據類型
- 5、怎麼學習golang
- 6、Golang 語言深入理解:channel
golang 怎麼定義可變參數的函數
golang定義可變參數的函數方法是:
—- 採用ANSI標準形式時,參數個數可變的函數的原型聲明是:
type funcname(type para1, type para2, …)
—- 這種形式至少需要一個普通的形式參數,後面的省略號不表示省略,而是函數原型的一部分。type是函數返回值和形式參數的類型。
—- 採用與UNIX System V兼容的聲明方式時,參數個數可變的函數原型是:
type funcname(va_alist)
va_dcl
—- 這種形式不需要提供任何普通的形式參數。
type是函數返回值的類型。va_dcl是對函數原型聲明中參數va_alist的詳細聲明,實際是一個宏定義,對不同的硬件平台採用不同的類型來定義,但在最後都包括了一個分號。因此va_dcl後不再需要加上分號了。va_dcl在代碼中必須原樣給出。va_alist在VC中可以原樣給出,也可以略去。
此外,採用頭文件stdarg.h編寫的程序是符合ANSI標準的,可以在各種操作系統和硬件上運行;而採用頭文件varargs.h的方式僅僅是為了與以前的程序兼容。所以建議使用前者。
Golang 遊戲leaf系列(六) Go模塊
在 Golang 遊戲leaf系列(一) 概述與示例 (下文簡稱系列一)中,提到過Go模塊用於創建能夠被 Leaf 管理的 goroutine。Go模塊是對golang中go提供一些額外功能。Go提供回調功能,LinearContext提供順序調用功能。善用 goroutine 能夠充分利用多核資源,Leaf 提供的 Go 機制解決了原生 goroutine 存在的一些問題:
我們來看一個例子(可以在 LeafServer 的模塊的 OnInit 方法中測試):
這裡的 Go 方法接收 2 個函數作為參數,第一個函數會被放置在一個新創建的 goroutine 中執行,在其執行完成之後,第二個函數會在當前 goroutine 中被執行。由此,我們可以看到變量 res 同一時刻總是只被一個 goroutine 訪問,這就避免了同步機制的使用。Go 的設計使得 CPU 得到充分利用,避免操作阻塞當前 goroutine,同時又無需為共享資源同步而憂心。
這裡主動調用了 d.Cb(-d.ChanCb) ,把這個回調取出來了。實際上,在skeleton.Run里會自己取這個通道
看一下源碼:
New方法,會生成指定緩衝長度的ChanCb。然後調用Go方法就是先執行第一個func,然後把第二個放到Cb里。現在手動造一個例子:
這裡解釋一下,d.Go根據源碼來看,實際也是調用了一個協程。然後上面兩次d.Go並不能保證先後順序。目前的輸出結果是1+2那個先執行了,把3寫入d.ChanCb,然後把3讀出來,繼續讀時,d.ChanCb里沒有東西,阻塞了。然後1+1那個協程啟動了,最後又讀到了2。
現在把time.Sleep(time.Second)的注釋解開,會是啥結果呢
這裡執行到time.Sleep睡著了,上面兩個d.Go仍然是不確定順序的,但是會各自的function先執行掉,然後陸續把cb寫入d.ChanCb。看這次輸出,1+2先寫進去的。所以最後執行d.Cb時,就把3先讀出來了。然後d.ChanCb的長度為1,說明還有一個,就是輸出2了。
另外,就是close時會判斷g.pendingGo
這個例子的意思很明顯,NewLinearContext這種方式,即使先調用的慢了半秒,它還是會先執行完。
這裡先是用了一個list,加入的時候用mutexLinearGo鎖了,都加到最後。然後新開協程去處理,讀的時候從最前面開始讀,也要用mutexLinearGo鎖。執行的時候,也要上鎖mutexExecution,確保f()執行完並且寫入g.ChanCb回調,這個mutexExecution鎖才會解除。現在可以改造一個帶回調的例子:
結果說明,確實是2先被寫入了d.ChanCb。
基礎知識 – Golang 中的格式化輸入輸出
【格式化輸出】
// 格式化輸出:將 arg 列表中的 arg 轉換為字符串輸出
// 使用動詞 v 格式化 arg 列表,非字符串元素之間添加空格
Print(arg列表)
// 使用動詞 v 格式化 arg 列表,所有元素之間添加空格,結尾添加換行符
Println(arg列表)
// 使用格式字符串格式化 arg 列表
Printf(格式字符串, arg列表)
// Print 類函數會返回已處理的 arg 數量和遇到的錯誤信息。
【格式字符串】
格式字符串由普通字符和佔位符組成,例如:
“abc%+ #8.3[3]vdef”
其中 abc 和 def 是普通字符,其它部分是佔位符,佔位符以 % 開頭(註:%% 將被轉義為一個普通的 % 符號,這個不算開頭),以動詞結尾,格式如下:
%[旗標][寬度][.精度][arg索引]動詞
方括號中的內容可以省略。
【旗標】
旗標有以下幾種:
空格:對於數值類型的正數,保留一個空白的符號位(其它用法在動詞部分說明)。
0 :用 0 進行寬度填充而不用空格,對於數值類型,符號將被移到所有 0 的前面。
其中 “0” 和 “-” 不能同時使用,優先使用 “-” 而忽略 “0”。
【寬度和精度】
“寬度”和“精度”都可以寫成以下三種形式:
數值 | * | arg索引*
其中“數值”表示使用指定的數值作為寬度值或精度值,“ ”表示使用當前正在處理的 arg 的值作為寬度值或精度值,如果這樣的話,要格式化的 arg 將自動跳轉到下一個。“arg索引 ”表示使用指定 arg 的值作為寬度值或精度值,如果這樣的話,要格式化的 arg 將自動跳轉到指定 arg 的下一個。
寬度值:用於設置最小寬度。
精度值:對於浮點型,用於控制小數位數,對於字符串或字節數組,用於控制字符數量(不是字節數量)。
對於浮點型而言,動詞 g/G 的精度值比較特殊,在適當的情況下,g/G 會設置總有效數字,而不是小數位數。
【arg 索引】
“arg索引”由中括號和 arg 序號組成(就像上面示例中的 [3]),用於指定當前要處理的 arg 的序號,序號從 1 開始:
‘[‘ + arg序號 + ‘]’
【動詞】
“動詞”不能省略,不同的數據類型支持的動詞不一樣。
[通用動詞]
v:默認格式,不同類型的默認格式如下:
布爾型:t
整 型:d
浮點型:g
複數型:g
字符串:s
通 道:p
指 針:p
無符號整型:x
T:輸出 arg 的類型而不是值(使用 Go 語法格式)。
[布爾型]
t:輸出 true 或 false 字符串。
[整型]
b/o/d:輸出 2/8/10 進制格式
x/X :輸出 16 進制格式(小寫/大寫)
c :輸出數值所表示的 Unicode 字符
q :輸出數值所表示的 Unicode 字符(帶單引號)。對於無法顯示的字符,將輸出其轉義字符。
U :輸出 Unicode 碼點(例如 U+1234,等同於字符串 “U+%04X” 的顯示結果)
對於 o/x/X:
如果使用 “#” 旗標,則會添加前導 0 或 0x。
對於 U:
如果使用 “#” 旗標,則會在 Unicode 碼點後面添加相應的 ‘字符’(前提是該字符必須可顯示)
[浮點型和複數型]
b :科學計數法(以 2 為底)
e/E:科學計數法(以 10 為底,小寫 e/大寫 E)
f/F:普通小數格式(兩者無區別)
g/G:大指數(指數 = 6)使用 %e/%E,其它情況使用 %f/%F
[字符串或字節切片]
s :普通字符串
q :雙引號引起來的 Go 語法字符串
x/X:十六進制編碼(小寫/大寫,以字節為元素進行編碼,而不是字符)
對於 q:
如果使用了 “+” 旗標,則將所有非 ASCII 字符都進行轉義處理。
如果使用了 “#” 旗標,則輸出反引號引起來的字符串(前提是
字符串中不包含任何製表符以外的控制字符,否則忽略 # 旗標)
對於 x/X:
如果使用了 ” ” 旗標,則在每個元素之間添加空格。
如果使用了 “#” 旗標,則在十六進制格式之前添加 0x 前綴。
[指針類型]
p :帶 0x 前綴的十六進制地址值。
[符合類型]
複合類型將使用不同的格式輸出,格式如下:
結 構 體:{字段1 字段2 …}
數組或切片:[元素0 元素1 …]
映 射:map[鍵1:值1 鍵2:值2 …]
指向符合元素的指針:{}, [], map[]
複合類型本身沒有動詞,動詞將應用到複合類型的元素上。
結構體可以使用 “+v” 同時輸出字段名。
【注意】
1、如果 arg 是一個反射值,則該 arg 將被它所持有的具體值所取代。
2、如果 arg 實現了 Formatter 接口,將調用它的 Format 方法完成格式化。
3、如果 v 動詞使用了 # 旗標(%#v),並且 arg 實現了 GoStringer 接口,將調用它的 GoString 方法完成格式化。
如果格式化操作指定了字符串相關的動詞(比如 %s、%q、%v、%x、%X),接下來的兩條規則將適用:
4。如果 arg 實現了 error 接口,將調用它的 Error 方法完成格式化。
5。如果 arg 實現了 string 接口,將調用它的 String 方法完成格式化。
在實現格式化相關接口的時候,要避免無限遞歸的情況,比如:
type X string
func (x X) String() string {
return Sprintf(“%s”, x)
}
在格式化之前,要先轉換數據類型,這樣就可以避免無限遞歸:
func (x X) String() string {
return Sprintf(“%s”, string(x))
}
無限遞歸也可能發生在自引用數據類型上面,比如一個切片的元素引用了切片自身。這種情況比較罕見,比如:
a := make([]interface{}, 1)
a[0] = a
fmt.Println(a)
【格式化輸入】
// 格式化輸入:從輸入端讀取字符串(以空白分隔的值的序列),
// 並解析為具體的值存入相應的 arg 中,arg 必須是變量地址。
// 字符串中的連續空白視為單個空白,換行符根據不同情況處理。
// \r\n 被當做 \n 處理。
// 以動詞 v 解析字符串,換行視為空白
Scan(arg列表)
// 以動詞 v 解析字符串,換行結束解析
Scanln(arg列表)
// 根據格式字符串中指定的格式解析字符串
// 格式字符串中的換行符必須和輸入端的換行符相匹配。
Scanf(格式字符串, arg列表)
// Scan 類函數會返回已處理的 arg 數量和遇到的錯誤信息。
【格式字符串】
格式字符串類似於 Printf 中的格式字符串,但下面的動詞和旗標例外:
p :無效
T :無效
e/E/f/F/g/G:功能相同,都是掃描浮點數或複數
s/v :對字符串而言,掃描一個被空白分隔的子串
對於整型 arg 而言,v 動詞可以掃描帶有前導 0 或 0x 的八進制或十六進制數值。
寬度被用來指定最大掃描寬度(不會跨越空格),精度不被支持。
如果 arg 實現了 Scanner 接口,將調用它的 Scan 方法掃描相應數據。只有基礎類型和實現了 Scanner 接口的類型可以使用 Scan 類方法進行掃描。
【注意】
連續調用 FScan 可能會丟失數據,因為 FScan 中使用了 UnreadRune 對讀取的數據進行撤銷,而參數 io.Reader 只有 Read 方法,不支持撤銷。比如:
golang原生數據類型
golang原生數據類型:
按長度:int8(-128-127)、int16、int32、int64。
無符號整型:uint8(0-255)、uint16、uint32、uint64。
int:32位操作系統上就是int32,64位操作系統上就是int64。
uint:32位操作系統上就是uint32,64位操作系統上就是uint64。
含義
Golang的引用類型包括slice、map和channel。它們有複雜的內部結構,除了申請內存外,還需要初始化相關屬性。對於引用類型,變量存儲的是一個地址,這個地址存儲最終的值。內存通常在堆上分配,通過GC回收。獲取指針類型所指向的值,使用:”*”取值符號。比如:var*pint,使用*p獲取p指向的值。
怎麼學習golang
隨着 PHP 有着越來越深入的了解,以及遇到越來越多的不同業務時,使用 PHP 總會讓我有一種莫名的無力感。當然,並不是我一個人在使用 PHP 的時候遇到了問題。事實上,每個略微有一些經驗,接觸過一些需求的人都會有同樣的困惑。各種配合 LAMP(或者LNMP?)架構的後端技術也因此被發明或被發現,進而整合到 PHP 的開發的技術體系中。從簡單的 Memcached作為數據中轉,cron 後端定時處理;到 Gearman、RabbitMQ 這些隊列神器;最近 Laruence 甚至封裝了利用 libcurl 的異步特性實現並發 RPC 調用的 yar 擴展。幾乎整個社區都在尋找 PHP 的摩西之路。好吧,說了一大堆,回歸主題。之前我寫了一篇英文練筆《Why you PHP guys should learn Golang》,獲得不少國際友人的關注。排除拼寫和語法被他們詬病外,主要是有許多朋友覺得我沒把事情說清楚。所以這裡我用母語重新聊聊這個事情,只是這些國際友人什麼時候能學會閱讀中文呢?;)Go 或者 Golang,是由 Google 支持的快速、一致、穩定的,有活躍的社區支持的開源編程語言。越來越多的應用選擇使用 Golang 進行構建。雖然 Rob Pike 說“… 我們希望 C++ 程序員來了解 Go 並作為一個可選的語言 …”,不過我真得認為:PHPer 應當學習 Golang! 接下來我們就來談談原因。容易學習PHP 相當容易學習。Golang 也是!在這點上,一群大老外對我的觀點進行了猛烈的抨擊。他們認為我羞辱了 PHPer,說得好像只有簡單的東西 PHPer 才能學會一樣。但是,這難道不是事實嗎?或者換個說法:像我一樣的喜歡 PHP 的人,或多或少都會更喜歡簡單的東西。PHP 的語法接近 C 族編程語言(C/C++/Java等等)。如果有這些語言的經驗,在第一次遇到 PHP 的時候立刻就能開始上手編寫代碼。在我看來,編寫 PHP 代碼或許更加考驗程序員的記憶力,而不是智力(當你面對各種不同風格的函數定義、各種擴展的特殊約定時,你一定會相當認同我的觀點)。Golang 同樣是一個 C 族編程語言。呃,或者有一些不同吧。例如關鍵字 “for”,功能上和 PHP 的接近,但是沒有括號。條件語句 “if” 同樣無需括號。可以閱讀 Effective Go 了解更多內容。Golang 只有 3025 個關鍵字和 47 個操作符號、分隔符號或其他特殊標記。記住這些標記確實不需要什麼特別的努力。精巧的類型系統相當容易使用。實用的,具有方法的結構體類型代替了笨重的對象系統。接口的設計是 Golang 中我最喜歡的部分。當完成了《Go 指南》的學習之後,利用 PHP 積累的經驗,立刻就可以開始使用 Golang 處理一些簡單的任務。容易使用PHP 腳本是由 SAPI 組件進行解析執行的,如 Web 服務器模塊、PHP-FPM 或者 CLI。部署 PHP 所需要的全部東西就是一個 SAPI 環境。配置這個環境對於新手來說可能是學習 PHP 過程中最為困難的部分。所有的 Golang 代碼會編譯和鏈接為本地碼。所以除了編譯環境,執行時無需再為其進行任何特別的部署。對比 PHP 環境的配置,這要簡單很多。你真得認為配置 PHP 環境很複雜嗎?我不覺得,真的!而配置 Golang 編譯環境比那還要簡單點。我確信已經有大量的 Golang 相關的書籍、文章介紹過如何進行編譯環境的配置了。為了更加清晰,我這裡梳理一下思路。
有三個步驟需要處理:下載Golang 的源代碼;根據《[翻譯]Go 環境設置》的提示設置環境變量;運行源代碼 src 目錄中的 all.bash。或者一步到位:使用二進制包進行安裝。然後就會得到一個叫做“go”的工具集合。使用“go”工具和使用 PHP 的 CLI 工具一樣簡單。《[翻譯]go 工具》對此進行了詳細的解釋。PHP 的迷思如果一個編程語言容易學習和使用,我們是不是就應當學習它呢?有許多容易學習和使用的編程語言。難道要把它們都學一遍?答案是顯然的:NO!但是 呢?只是因為它很酷!是的,我在開玩笑,但是這是真的。無論如何先從 PHP 自身談起吧。PHP “原本是為了開發動態的 Web 頁面而設計的服務器端通用語言(Wikipedia)”。PHP 一個重要的特性就是可以嵌入到 HMTL 中。代碼編寫在“?php … ?”標籤內;HTML 寫在標籤外。它有一個強大的擴展系統。擴展使用 C 調用 Zend API 編寫。數據的處理實際上要利用這些擴展完成。在我看來,PHP 是世界上最好的模板語言。但是當積累了一些 PHP 的經驗,並且開始面對一些更加複雜的 Web 應用時,你一定會對 PHP 產生一種無力的感覺。它沒有內建的並行機制,沒有線程、進程(你真得認為那個簡陋的進程控制可以不加改造的用在高並發的生產環境?),或者其他某“程”。一個慢數據源可以阻塞整個頁面的處理。消息隊列、緩存、代理……系統開始不僅僅是 PHP 這麼單純,還包括了許多服務和系統組件。這時,PHP 只處理很少的業務邏輯,成為真正的模板語言了。PHPer 們總是在尋找解決這一問題的辦法,如“PHP multithread”或者PHP RPC 並發框架。我很難說哪種會更好一些。不過我肯定你會需要選擇一些編程語言用於後端工作的開發。就我自己的經驗,我嘗試過 C(一直在和 malloc/free 進行搏鬥)/Java(陷入到了 jar 地獄中)/Python(從來沒能做到 Pythonic 不說,還總是在錯誤的類型中打轉)……如果想要獲得性能,就得同內存管理進行搏鬥;如果用 GC,就得部署和調優 VM;當獲得便利性的時候,同時也是走在刀尖上,一個小錯誤就引起巨大的災難……每個都有優勢,同樣每個都有問題。好吧!現在回到 Golang!Golang 有 GC,無需關心內存管理(或者可以用較少的精力去關注它)。代碼被編譯為本地碼,因此“cp”和“mv”就是部署 Golang 編寫的應用所需要的全部工具。噢,我剛才已經說過了,Golang 是一個具有靜態類型系統的編譯語言。所以你沒有機會弄亂變量的類型。當然,PHPer 應該學習 Golang 的一個重要原因是“轉到Go 是因為他們並未放棄太多的表達能力,但是獲得了性能,並且與並發共舞(Rob Pike)”。《Why Not Go?(英文)》對此進行了深入的分析。我可以分享一些我的經驗:有一個 Gearman 的worker 用於處理後端數據。PHP 通過其 API 連接到 Gearman 的 Job Server 向 worker 發起請求。最初 worker 是使用 python 編寫的(還有更加原始的版本,PHP 的,但是你能想像它工作起來……唉,不說了……)。這個版本有許多的問題(是我們自己的問題,不關 Python 的事),但是至少它能工作。後來用 Golang 重寫了這個 worker。為此我開發了 Golang 的 Gearman API,並使用 Zend API 編寫了一個在 Golang 中執行 PHP 腳本的包。然後將它們放在一起:一個可以執行 PHP 的 Gearman worker。它已經工作了一段時間了,看起來還不錯!哦,受到 Yar 的啟發,這裡還有一個 Golang 編寫的 RPC 合併器,用來合併 PHP 腳本中的 RPC 調用。現在還是個玩具,不過或許日後能用得着。這其實是將 Golang 的 channel 當作消息隊列來用。我在《Golang:有趣的 channel 應用》中對此有一些說明。世界真美好啊。謝謝 Golang!無論如何,大多數 PHPer 在進行後端開發的時候都會需要學習一些其他語言。如果你正在尋找,或者已經嘗試了一些其他語言。為什麼不來試試 Golang?它真得可以讓你的生活更加輕鬆和快樂。讓你可以有更多的時間陪伴你的家人和朋友,吃你愛吃的東西,去你想去的地方。貌似我還是沒說清楚啊?好吧,沒關係,在下個月的中國軟件開發者大會上再跟大家就這個話題做一個探討吧。
Golang 語言深入理解:channel
本文是對 Gopher 2017 中一個非常好的 Talk�: [Understanding Channel](GopherCon 2017: Kavya Joshi – Understanding Channels) 的學習筆記,希望能夠通過對 channel 的關鍵特性的理解,進一步掌握其用法細節以及 Golang 語言設計哲學的管窺蠡測。
channel 是可以讓一個 goroutine 發送特定值到另一個 gouroutine 的通信機制。
原生的 channel 是沒有緩存的(unbuffered channel),可以用於 goroutine 之間實現同步。
關閉後不能再寫入,可以讀取直到 channel 中再沒有數據,並返回元素類型的零值。
gopl/ch3/netcat3
首先從 channel 是怎麼被創建的開始:
在 heap 上分配一個 hchan 類型的對象,並將其初始化,然後返回一個指向這個 hchan 對象的指針。
理解了 channel 的數據結構實現,現在轉到 channel 的兩個最基本方法: sends 和 receivces ,看一下以上的特性是如何體現在 sends 和 receives 中的:
假設發送方先啟動,執行 ch – task0 :
如此為 channel 帶來了 goroutine-safe 的特性。
在這樣的模型里, sender goroutine – channel – receiver goroutine 之間, hchan 是唯一的共享內存,而這個唯一的共享內存又通過 mutex 來確保 goroutine-safe ,所有在隊列中的內容都只是副本。
這便是著名的 golang 並發原則的體現:
發送方 goroutine 會阻塞,暫停,並在收到 receive 後才恢復。
goroutine 是一種 用戶態線程 , 由 Go runtime 創建並管理,而不是操作系統,比起操作系統線程來說,goroutine更加輕量。
Go runtime scheduler 負責將 goroutine 調度到操作系統線程上。
runtime scheduler 怎麼將 goroutine 調度到操作系統線程上?
當阻塞發生時,一次 goroutine 上下文切換的全過程:
然而,被阻塞的 goroutine 怎麼恢復過來?
阻塞發生時,調用 runtime sheduler 執行 gopark 之前,G1 會創建一個 sudog ,並將它存放在 hchan 的 sendq 中。 sudog 中便記錄了即將被阻塞的 goroutine G1 ,以及它要發送的數據元素 task4 等等。
接收方 將通過這個 sudog 來恢復 G1
接收方 G2 接收數據, 並發出一個 receivce ,將 G1 置為 runnable :
同樣的, 接收方 G2 會被阻塞,G2 會創建 sudoq ,存放在 recvq ,基本過程和發送方阻塞一樣。
不同的是,發送方 G1如何恢復接收方 G2,這是一個非常神奇的實現。
理論上可以將 task 入隊,然後恢復 G2, 但恢復 G2後,G2會做什麼呢?
G2會將隊列中的 task 複製出來,放到自己的 memory 中,基於這個思路,G1在這個時候,直接將 task 寫到 G2的 stack memory 中!
這是違反常規的操作,理論上 goroutine 之間的 stack 是相互獨立的,只有在運行時可以執行這樣的操作。
這麼做純粹是出於性能優化的考慮,原來的步驟是:
優化後,相當於減少了 G2 獲取鎖並且執行 memcopy 的性能消耗。
channel 設計背後的思想可以理解為 simplicity 和 performance 之間權衡抉擇,具體如下:
queue with a lock prefered to lock-free implementation:
比起完全 lock-free 的實現,使用鎖的隊列實現更簡單,容易實現
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/279854.html