01.16 Go語言魅力初體驗

Go語言簡介

Golang是Google在2009年開源的編程語言。我們思考一個問題,Google為什麼會在2009年開源Golang呢?其實內部有幾個動意,Google於2009年之前在Python上面投入了非常大的精力。Python這門語言由於它的語法簡潔,所以使用範圍非常廣泛。但是,這樣就會涉及到一個問題。一門編程語言簡單它是一件非常厲害的事情。

而Golang設計者Rob Pike說要把Golang這門編程語言的哲學設計極致。它的特徵是沒有什麼內容可以再刪除了,而不是沒有什麼內容可以再添加進去了。如果一門編程語言入門非常困難,從人群上定義,也許只有10%的人能學會。如果把它設計的在簡單一些,可能會有20%的人學會,如果在經過幾次優化,把它設計的更加的簡單,可能90%的人都可以學會。

Python語言本身就比較簡單,所以導致了Python這門編程語言性能不是最強的,也不需要編譯等種種原因。導致現在運維在用它、測試在用它、大數據在用它、包括現在非常熱門的人工智行業能也在用它。甚至幼兒編程、小學編程、學齡前兒童編程和一些小孩兒玩的機器人都是拿Python寫出來的。

簡單的東西並不是可以簡單做出來的,但是讓它做出來就會非常的厲害。我當時學Golang這門語言的時候就覺得它非常的厲害,因為它也是非常簡單的。所以說,當時Google在沒有Golang這門編程語言之前,他們選擇是用Python。但是Python有兩個硬傷:首先它運行速度是非常慢的,比如說C語言運行需要1秒鐘時間,而Python平均是需要60秒。並不是說Python實現的不好,因為Python是解釋型語言,所以跑的比較慢。當然,其它的解釋型語言也不比Python語言強到哪裡去。包括我們業界統稱的3P語言(Python、PHP、Perl)都處於這個水平線,以及Ruby語言也是如此。

當然,Google也是付出了非常大的時間和精力,想從Python解釋器的實現層面去想辦法。當時他們想把Python這門語言做的非常高效,甚至Google把Python語言的發明人GuidovanRossum都招到了Google公司。這樣的一群大牛搞了很久,最後發現這件事貌似是非常困難的。因為它歷史包袱太重了,有很多庫已經基於Python這麼做了,改動任何一點內容都會導致兼容性出現崩潰的情況。包括裡面一些代碼的風格、方式都和高性能設計不相符。所以說,優化起來也是非常困難的。

Python語言它是解釋型語言。解釋型語言是什麼意思呢?運行一個Python腳本,使用ps命令看該腳本時,你會發現.py文件並不是用ps aux命令可以查看到的,它實際運行的是Python。但是用編譯型語言編譯出來是什麼樣,它就是什麼樣。比如Nginx這種編譯型的程序,它就是一個Nginx程序。用Golang編譯出來程序,他就是用Golang運行的。

Python的一個簡單原理是:當Python讀取.py文件時會去理解它每一步想做什麼,從頭到尾,一步一步往下執行,最後展開執行,這是解釋型語言。直接跑到語言裡面,彙編指令集,直接cpo,mov多少個計算器,load某個東西到內存裡面,最後開始運行,Python是這樣的解釋。

但是,Python並不是因為解釋而帶來這麼大的overhead,它最大的overhead(天花板)是在於Python是一個解釋型語言。所以它裡面所有的東西都是object(對象)。為什麼它又是一個object呢?其實它也可以不是一個object。它是object並不是因為它是一個解釋型語言,解釋型語言它不一定是object,它是object一個強原因是Python是一個弱類型語言。因為在Python裡面可以寫a = 1,代碼如下所示。

>>> a = 1

一個Python的腳本只寫a = 1這一行代碼也是可以的。意思也表達的非常明確,我把1賦值給了a。而且Python還支持在後面直接運行a = “b”,代碼如下所示。

>>> a = 1
>>> a = "b"

從上面代碼中可以看出1和b顯然是不一樣的。最起碼1是一個int類型,b是一個字符串,Python它為什麼允許把一個變量首先都不用聲明直接賦值給1呢?因為Python是一個弱類型語言。它在變量裡面沒有強制要求必須是個什麼樣的類型,就像編譯型語言C語言和Go語言裡面的指針一樣的東西,你愛指哪指哪,或者可以解釋為是一種動態的內容。這就會導致一個問題,a愛指哪指哪。所以說它只能在內存裡面,它是一個object。如果直接在a裡面存一個1或者在內存裡面存一個int大小的一個1,或者又有人把它賦值成字符串,這豈不是抓瞎了嗎。就是因為Python是一個弱類型語言,也是因為這個原因導致的很難優化。

什麼是弱類型語言呢?弱類型語言比較通俗易懂,假設你找一個從來沒有學過腳本語言的人去學Python,你告訴他a = 1,a = “b”,他並不覺得有什麼不妥,除非懂計算機的人知道它一會是個int,一會是個string覺得很奇怪,所以,它的門檻會大大降低。不然的話,a = “b”,他會告訴你is not a string type,Please......。結果剛入門的人看到這個報錯後就會不學了,存在的是這樣的一個問題。

為什麼是object就跑的慢呢?仔細想想,我們現在寫了一個a = 1,b = 2,c = a + b,代碼如下所示。

>>> a = 1
>>> b = 2
>>> c = a + b
>>> print (c)
3

按照上面代碼所示寫這樣一段代碼,如果是解釋型弱類型語言它是什麼樣的過程呢?它首先會創建一個Object,可能會是一塊內存,Object裡面可能會存入很多的信息,可能會存一個type的東西,可能會保存type是一個int,後面可能是buffer size。因為它有可能存的是字符串,所以我們需要知道指定的buffer size是多大。然後會有一個指針p指向1的一個內存。p = & 1(P指向等於1的一個內存的地方),它會是這樣一個東西。然後相應的b = 2,它就會有一個內存指向p指向2,最後做相加的時候就會變得很複雜,首先要找到Object a,在Object a裡面找到p這個指針指向的一個內存,然後從p指針指向的內存裡面找到1,找到1之後,先放到一個地方,然後再去找b,同樣方法找到b的2,然後把a和b弄起來。然後發現c又是一個Object,然後在new一個object出來,然後分配type,分配buffer size等各種各樣的東西,因為裡面可能會涉及到gc和引用等各種各樣的東西,然後有了這些東西之後,我在把1+2的這個值3去set到一個內存裡面,在拿c的這個指向數據的指針再去指向3的內存。好,以上是關於c = a + b的整個執行過程。

雖然說了這麼多,但是CPU也是這麼執行的。可能說這只是幾納秒的一個操作,但是想想,如果是一個編譯型的強類型語言,會是一個什麼狀態?它是不是會直接翻譯成甚至優化過來,它直接發行a是一個int,b是一個2,它是一個確定了的值了。那麼a + b我不說編譯型優化,可能編譯型優化會直接優化成c = 3。就算不優化它也是直接找到內存,他的變量名可能在內存中都不存在了,直接找到放了a這個內存的變量,可能是在全局變量裡面。然後加上b的變量,大概就是兩個尋址。然後在一個CPU裡面,先Load到CPU計算器,在Load到計算器二中,在把+放到那一邊的內存中,整個過程執行完畢。一般來說會比弱類型的解釋型語言要快兩個數量級左右。因為它要做的事情非常非常少,它不需要分配很多很多的內存,所以說差距也是非常非常大的。各位讀者是否明白他為什麼慢?

那麼,各位讀者有沒有疑問,強類型語言或者說編譯型語言可以這樣做,Python只能那樣做,那麼Python有沒有什麼好的方法呢?

Python是一個解釋型弱類型語言,Python在這些方面也會進行優化,它會想辦法去優化自己的運行速度。比如說,Python在運行的時候你會發現在目錄裡面會產生一些.pyc文件,.pyc文件是優化的一種方式,但是.pyc文件並沒有改變它本身這種解釋型弱類型語言的方式,pyc僅僅是把.py文件做了一個預處理。意思是說,首先把註釋去掉,把長變量名替換成pyc內部裡面可以引用的短變量名,做一些其他的東西去優化,相當於是先預處理下py文件,這是一個簡單的優化過程。Python小數字裡面的優化也非常多,但是這些小數字優化措施僅可能讓他少那麼一兩倍,但是並不能改變和編譯型語言兩個數量級的差異。

如果你是Python的設計者,我能不能借用一下強類型語言這種概念去優化Python呢?我不想去弄這麼多的Object去做一件事情。實際上,如果我這個解釋器稍微有點思維,其實Python解釋器也是這麼做的。首先會把語法解釋成語法數,它會推演。現在c = a + b,OK,我發現a和b,那麼c是怎麼得到的?是用a+b得到的。a可能是怎麼算出來的,一步步推演出來的,如果這個解釋很容易推演出來,c其實就是1+2的結果。這個解釋器可不可以智能一點?我發現了1+2, 其實Open Object和銷燬Object這一件事是完全沒有必要的,如果稍微做一點優化,它其實可以發現C可以通過兩塊內存的東西load到內存中,就像編譯型內存一樣,load進去後,加起來放進去就可以了。如果C裡面還有後續的東西,給C分配一個Obeject存起來就可以了。這是一個非常不錯的思路,那麼有沒有人做這樣的事情呢?當然是有的,那就是JIT。

好,前面說了很多關於Python還有強弱型語言的介紹,那麼我們繼續來說一下Go語言的介紹。後面Google優化Python優化不動了,所以他們就自己想自己開發一門語言,於是就拿Google的前兩個字母就當這門語言的一個名字,開始設計這門語言,設計這門語言剛開始的時候是兩個人,分別是Rob Pike和Ken Thompson。

Rob Pike是自加拿大的程序員,曾經加入貝爾實驗室,為 UNIX小組的成員。曾經參與過貝爾實驗室九號計劃、Inferno,與編程語言 Limbo的開發。他與肯·湯普遜共同開發了UTF-8。

Ken Thompson是美國計算機科學學者和工程師。黑客文化圈子通常稱他為"ken"。在貝爾實驗室工作期間,Ken Thompson設計和實現了Unix操作系統。他創造了B語言(C語言的前身),而且他是Plan 9操作系統的創造者和開發者之一。2006年,Ken Thompson進入Google公司工作,與他人共同設計了Go語言。他與丹尼斯·裡奇同為1983年圖靈獎得主。 此外,Ken Thompson還參與過正則表達式和UTF-8編碼的設計,改進了文本編輯器QED,創造了Ed編輯器。他曾製造過專門用於下國際象棋的電腦"Belle",並創建了殘局數據庫。

關於Go語言設計者的照片可以觀看圖1。

Go語言魅力初體驗

圖1

Go語言特性

1.靜態編譯,它是一個編譯型語言,和C/C++類似。Go的編譯器去讀源代碼,把整個語法進行一遍解析,做一定的優化,優化完把它翻譯成當前目標機器的機器碼。當然,也可以做一些交叉編譯,比如在Mac系統上可以編Linux,不同的CPU版本都是可以再一臺機器上完成的。而且Go這門語言的厲害之處在於它是靜態編譯的,比如說我們打開一臺Linux的服務器。ldd命令可以查看文件或程序所依賴的動態庫,當然這僅限於編譯型東西才有。

比如我們簡單看下ls這個命令,ls命令指向的是/bin/ls,使用ldd查看/bin/ls所依賴的庫有哪些,如圖2所示。

Go語言魅力初體驗

圖2

如何查看一個文件或者程序是不是編譯型呢?我們通過file命令可以查看,比如查看/bin/ls這個程序,如圖3所示。如果是ELF就表示是編譯型語言。如果有動態庫就是鏈出來的。

Go語言魅力初體驗

圖3

動態庫主要是用來優化性能和優化內存,比如說很多程序都用到了這些libc、libacl,我就只用在系統中佔一份內存,這是一個古老的考慮。但是這也帶來了一個問題,我編譯一個程序它依賴的動態庫如果它在機器上的版本不對,或者不存在它就運行不了,後面會給它的部署或者其它的一些事情帶來很大的問題。做過運維的人想必都清楚這件事情,它叫dependence hell(依賴地獄)。因為這些動態庫非常噁心,比如A動態庫又依賴於B動態庫,它簡直就像我們捅了馬蜂窩一樣。本來只想喝點蜂蜜,沒想到捅到一堆馬蜂蜇了一臉包,典型的賠了夫人又折兵。Linux很多發行版解決最大的問題就是包的依賴問題,為什麼有包的依賴問題就是因為這些問題。

Go語言的一個奇特之處、創新之處在於有人也認為它是歷史的倒退,它默認是靜態編譯。如果你看過Go語言編出來的任何程序你會發現它的依賴幾乎為零。幾乎為零是什麼意思呢?我們用Go語言寫的Hello World代碼來舉例。在Linux服務器上編譯Hello World這段代碼,先創建一個Hello.go文件。在Hello.go文件中粘貼如下所示代碼。

package main
import "fmt"
func main() {
fmt.Println("hello golang")
}

go build編譯hello.go文件,命令如下所示。

[root@golang ~]# go build hello.go 
[root@golang ~]# ll
total 1880
-rw-------. 1 root root 1319 Jul 4 17:26 anaconda-ks.cfg
-rwxr-xr-x 1 root root 1902849 Nov 18 22:44 hello
-rw-r--r-- 1 root root 71 Nov 18 22:43 hello.go
-rw-r--r-- 1 root root 500 Nov 10 22:01 hello.php
drwxr-xr-x 21 root root 4096 Nov 7 16:09 Linux
drwxr-xr-x 3 root root 4096 Nov 17 19:09 www

運行.hello可執行程序,命令如下所示。

[root@golang ~]# ./hello 
hello golang

使用file命令查看文件的類型也是二進制的ELF類型,命令如下所示。

[root@golang ~]# file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

使用ldd 查看./hello可執行程序我們看到的結果會很失望,因為它並沒有依賴任何一個動態鏈接庫,顯示not a dynamic executable,如下所示。

[root@golang ~]# ldd ./hello
not a dynamic executable

那麼疑問來了,它不依賴任何動態鏈接就可以運行嗎?此處還要在給各位讀者介紹一些內容,不然還是無法理解其中的奧妙和含義。

為什麼程序裡面有動態鏈接庫和非動態鏈接庫,非動態鏈接庫的代碼難道就可以隨處運行了嗎?比如說把Linux這個版本拷貝到Mac上或者拷貝到Windows上能否運行呢?它到底依賴什麼東西呢?非動態鏈接庫的版本,非動態鏈接executable它只依賴內核的API,它不依賴於lib這種API。如果是一個普通的程序C/C++編譯的它一般默認會依賴於動態的鏈接庫。它可能會調用內核的API也可能會調用libc的,它只要調用就會有依賴,但是這種非動態的這種程序,它的依賴只有內核,只要內核的API滿足它的要求他就能運行,而且還有一個好消息,由於Linus Benedict Torvalds同學不懈的堅持,就導致內核從2.X版本到現在3.X和4.X版本內核的接口都是兼容的。

比如我編譯了一個面向2.6內核的一個版本的executable,我可以放到4.X版本的內核中繼續跑,它的內核就保證了這麼一件事,我在2.6版本提供接口在4.X版本里面繼續提供,如果有修改我不修改,我增加新的。所以說,Go語言寫出來的二進制的這種依賴性是非常非常乾淨的。CentOS7版本可以拿過來直接使用,這是它的一個厲害之處。而CentOS7之前的系統,尤其是CentOS6的操作系統可能會裝一些其他的程序來達到正常使用的效果。

為什麼在靜態編譯這裡說了這麼多內容,我感覺這是Go語言之所以現在大行其道的關鍵性原因之一。因為大家長久以來要麼被依賴噁心,要麼被腳本低下的執行效率噁心,就算是Python它也存在很多的依賴,寫過Python腳本的人都知道。當然,Java也存在很多的依賴庫。

2.垃圾回收,這個現在也是見怪不怪了,基本上現在所有的編程語言,除了C(C++有一些智能指針的東西可以做垃圾回收)語言以外都內置了自動垃圾回收。因為內存這件事對於很多程序員來說對於心智負擔太重了,很容易洩露,但是我覺得還好吧。並沒有覺得是一個很大的負擔。而且帶垃圾回收的語言在一個行業上是無法使用,比如高頻交易、量化投資這些行業它不會用垃圾回收語言,因為它會有gc和停頓。

3.簡潔的符號和語法:Go語言的語法現在來講算是語法簡潔性第一陣營裡的,它的語法結構不比Python多,也比PHP和Ruby少很多。

4.平坦的類型系統:Go語言中的類型系統還算是比較平坦的,它沒有太多的很複雜的類型,它的類型控制的很好,基本上最高層面類型大概在在10個以內就可以包含所有的類型。

5.基於CSP的併發模型:

6.高效簡單的工具鏈:Go語言做出來時他們很在意一件事情:編譯速度。項目用Go去build需要多長時間。這個在很多沒有接觸過超級大項目的人來說覺得我編譯還需要編譯很久嗎?但是在一些大的系統,比如像微軟和Google這種公司,編譯一下他們大的產品的代碼庫編譯可能需要一天,並不是說機器不行,機器都是頂級的(SSD碩大無比,CPU和內存都很大)。但是還是要編一天。這也是一個老生常談的問題。C/C++編有的時候太慢,Go語言在編譯上要比C/C++快很多。這也是因為Google內生的一個需求。

7.豐富的標準庫:它說的是豐富的標準庫而不是豐富的庫。如果拿豐富的庫來說,Go現在有非常多的庫,語言好用了、火了後庫自然而然就很多了。豐富的標準庫是說很多東西你不需要去引用github上的任何東西都是可以在內部做到的。但是Go語言和PHP、Python、Ruby、Perl語言比起來,它的庫也沒比前面提到的幾個語言庫豐富太多。基本上是一個水平,基本上常見的需求都不需要去在外部找一些東西去實現。

比如Go語言裡面內置了一個HTTP的庫,包括Server和Client。這樣的一個好處也是衍生的,我並不清楚她們的設計者可能預期沒預期到這麼一個結果。很多時候拿Go寫的程序它不需要搞個Nginx去Save,基本上一個文件就可以把自己所有的功能全部融入進去。甚至有點天方夜譚的是各位讀者可能現在覺得有些詭異。如果大家做過網站的開發或運維就會發現,網站的開發或運維有很多鬆鬆散散的文件,首先源碼文件、Template文件、JavaScript、Images和CSS等一大堆亂七八糟的文件。但是拿Go的一些外部框架去寫會發現很神奇的一點,Go語言可以把所有的文件整合成一個文件。這個拷貝到任意地方這個網站直接運行該文件都可以啟動成功。連Nginx都不需要,就是因為它內置了一個HTTP的庫,性能而且還不錯。對比Nginx可能還有一些差距,但是它屬於第一陣營的吧,還是比較不錯的。

當然,可能有些讀者會有疑問,這個豐富的標準庫和前面所講到的動態庫有什麼區別呢?這個標準庫意思是說Go是靜態編譯的,它會把你用到的這些標準庫給他編到Go的執行文件裡,這是它的標準庫。而前面說到的動態庫它是Linux構成的一個基礎,一般存在於系統層面的東西。可以通過yum或等方式進行安裝,也可以自己去帶。動態庫一般是隻提供了一種動態鏈接的一個so,如果你的二進制編完了,就可以直接鏈上去用。當然,你在編的時候C/C++要有很多頭文件去指引著它去用這個東西。

那麼動態庫和靜態庫又有什麼區別呢?作為一個用戶來講,靜態庫不需要去管,因為這個東西全部包進去了。動態庫是需要滿足它的動態庫的各種依賴、版本的要求才能去使用。動態庫可以自己帶so,如果不帶so要依賴於目標部署機器。

靜態庫在系統上存在都是.a的擴展名,這些庫它是在編譯的時候才會鏈進去,如圖4所示。

Go語言魅力初體驗

圖4

而動態庫它是.so的擴展名,如圖5所示。

Go語言魅力初體驗

圖5

靜態庫只是在編譯時、鏈接時鏈進去的,對於Go語言來講,它相當於它內置的全是靜態庫,當然它並不是用的圖4中的那種格式,有興趣的話可以再去file一下圖4中的鏈接,它會告訴你它是一個靜態庫,代碼如下所示。current ar archive的意思就是靜態庫。

[root@golang ~]# file /usr/lib64/libbsd-compat.a 

/usr/lib64/libbsd-compat.a: current ar archive

在Windows系統上動態庫就是Dll文件,而靜態庫我們是看不到的,因為Windows不會把靜態庫放到我們家用的系統版本中。它只會放到開發的那種系統上。

Go語言開發環境安裝

2.1 安裝Windows版Golang SDK

現在我就來配置在Go語言在Windows系統下的環境,首先我們需要去官網下載Golang的安裝程序,下載地址如下所示。https://golang.org/dl/。打開該下載地址會看到如圖6所示的界面。

Go語言魅力初體驗

圖6

目前,最新的穩定版本是1.11.2,我們只需下載go1.11.2.windows-amd64.msi這個安裝包即可。雙擊鼠標左鍵打開Golang的支持庫,打開後會看到如圖7所示的安裝界面。

Go語言魅力初體驗

圖7

一直單擊鼠標左鍵選擇Next,當出現如圖8所示的界面時,可選擇自定義的路徑,此處作為默認不更改。

Go語言魅力初體驗

圖8

繼續單擊鼠標左鍵選擇Next按鈕,在單擊Install按鈕即可進行安裝,緊接著就可以看到Go語言正處於安裝中。安裝執行完畢後會出現如圖9所示的界面,單擊Finish按鈕即可。

Go語言魅力初體驗

圖9

我們已經成功安裝了Go語言,但是還需要進行一下驗證,按住鍵盤上的Win+R鍵會打開運行程序窗口,在運行程序窗口輸入cmd並按Enter鍵進入cmd命令窗口,直接在cmd命令行窗口鍵入go字符,如果出現如圖10所示的命令就表示Go語言環境配置成功了。

Go語言魅力初體驗

圖10

2.2 在Windows系統中安裝Go語言 IDE Goland

打開Jetbrains的官方網站,在Jetbrains的官網上下載我們Go語言開發所需要的IDE Goland。Goland是一款非常強大的IDE,我們在它的基礎之上就可以輕鬆並愉快的開發Go語言程序。

Goland下載鏈接地址:https://www.jetbrains.com/go/?fromMenu。

下載完Goland.exe可執行程序後雙擊打開goland-2018.2.4,啟動後會看到如圖11所示的安裝界面。

Go語言魅力初體驗

圖11

單擊Next按鈕進行下一步,會讓我們選擇該程序的安裝路徑,默認即可(或自定義),單擊Next按鈕初選如圖12所示的界面,選擇64位和關聯.go文件。

Go語言魅力初體驗

圖12

單擊Next按鈕下一步,Install下一步,然後就開始安裝Goland IDE了,安裝完成後單擊Finsh按鈕即可完成安裝,和前面安裝Go語言SDK步驟類似。

Goland安裝成功後,會在桌面上顯示一個JetBrains GoLand 2018.2.4的快捷方式。雙擊該軟件啟動Goland,它會提示如圖13所示的界面,默認我們選擇不去配置它(Do not import settings)。

Go語言魅力初體驗

圖13

單擊OK按鈕它會讓我們輸入註冊碼,如圖14所示。

Go語言魅力初體驗

圖14

最上面的Activate表示激活,右邊的Evaluate for free表示評估此軟件,俗稱試用。此處筆者選擇Activate激活它,下面有三個選項。第一個表示jetbrains賬戶,第二個表示激活碼,第三個表示Lincense 服務器。筆者此處選擇第二個選項,使用從Jetbrains購買的激活碼進行激活,如圖15所示。

Go語言魅力初體驗

圖15

單機OK按鈕即可啟動Goland IDE,界面Jetbrains設計的也非常的炫酷,如圖16所示。

Go語言魅力初體驗

圖16

啟動成功後,先帶領大家簡單做一下測試。選擇New Project,首先是選擇代碼的安放路徑,其次是選擇Go語言的SDK,也就是我們第一節所配置Go語言SDK,選擇後,如圖17所示。

Go語言魅力初體驗

圖17

點擊Create按鈕完成項目的創建,如圖18所示。

Go語言魅力初體驗

圖18

選中01.Go語言魅力初體驗目錄,右鍵新建一個Directory(目錄),名字叫做Hello Golang,新建成功後會在01.Go語言魅力初體驗目錄下面顯示一個Hello Golang的目錄,而且在對應的目錄下面也會顯示我們剛剛創建的這個Hello Golang目錄,如圖19所示。

Go語言魅力初體驗

圖19

在Hello Golang目錄下面去創建一個叫做Hello World的Go File(Go文件),但是提示我們需要設置Go的環境變量,如圖20所示。

Go語言魅力初體驗

圖20

這個GO PATH就是我們在第一節中安裝Golang SDK時所選擇的路徑,我默認選擇的是C:\\Go,單機右邊的“+”號按鈕進行添加Golang的環境變量,如圖21所示。

Go語言魅力初體驗

圖21

單擊OK按鈕後你會發現Goland已經不再提示讓我們設置GO PATH了。現在我們還需要在配置一下Goland,按住Ctrl+Alt+S鍵打開我們的Settings。點擊Editor選項,選中Font設置我們的字體大小。字體大小可根據顯示器大小進行設置,此處設置為22,字體個人比較喜歡Consolas,故設置成Consolas。然而下面的Color Scheme選項也可以進行一些個性化的設置,比如找到Go,我們可以設置Golang一些相關的高亮顯示,如圖22所示。

Go語言魅力初體驗

圖22

下面的Code Style可設置縮進等等相關的,默認配置的就是4個空格表示一個縮進。

Version Control可設置登陸Github,如圖23所示選擇輸入github的賬號和密碼即可完成登陸,登陸成功後顯示如圖24所示的界面。

Go語言魅力初體驗

圖23

Go語言魅力初體驗

圖24

全部設置完畢後,我們單擊Apply按鈕進行應用,應用成功後單機OK按鈕即可退出Goland的設置界面,此時你會發現代碼的顏色已經由白色背景了黑色背景,而且代碼的顏色也隨之發生了改變。

接下來我們寫一個Hello World的程序,因為這是每門編程語言最開始都要寫的一段代碼,讓這段代碼來向世界講述著它的來臨,代碼格式如下所示。

package main

import "fmt"

func main() {
fmt.Print("Hello World")
}

右鍵選中我們的Hello World.go文件,選擇Run Go build Hello World,或者按快捷鍵Ctrl+Shift+F10運行,運行結果如圖25所示。

Go語言魅力初體驗

圖25

關於Golang的Hello World每個語法是幹什麼用的,本節暫不做解釋,隨著我們後面的學習我相信各位讀者會慢慢了解的!

2.3 配置Windows系統Lite IDE

本節我們來使用下Lite IDE,它屬於輕量級的Go語言IDE,它可以把Go語言變成EXE。Lite DIE可在本書所提供的Gitlab上進行下載,下載地址如下所示。

下載完成後進行解壓操作,解壓後將Lite IDE拷貝到Go語言環境安裝目錄的根目錄下,然後將C:\\Go\\liteidex35.2.windows-qt5.9.5\\liteide\\bin目錄的liteide.exe啟動程序發送到桌面,回到桌面啟動Lite IDE啟動程序,啟動成功後顯示如圖26所示的安裝界面。

Go語言魅力初體驗

圖26

選擇左上角的文件,點擊新建,或者按快捷鍵Ctrl+N鍵進行快捷創建,我們選擇第二個Go Source File,路徑我們可以在01.Go語言魅力初體驗新建一個叫做Lite HelloWorld的目錄,文件名叫做LiteHelloWorld,最後單擊OK按鈕即可,如圖27所示。

Go語言魅力初體驗

圖27

注意:如果彈出對話框問我們是否建立加載,我們選擇YES即可。

這個Lite IDE也比較好用,我剛創建的Hello World會自動加載代碼,如圖28所示。

Go語言魅力初體驗

圖28

選擇頂部菜單欄中的編譯,選擇BuildAndRun進行編譯並運行,結果如圖29所示。

Go語言魅力初體驗

圖29

所以,到此為止,Lite IDE我們就配置成功了。我們打開01.Go語言魅力初體驗\\LiteHelloWorld目錄會看到下面有一個LiteHelloWorld.exe的可執行程序。這個可執行程序就是我們剛才把Go語言的程序變成了EXE的可執行文件程序。

打開cmd命令窗口,進入到01.Go語言魅力初體驗\\LiteHelloWorld目錄下把LiteHelloWorld.exe複製到cmd命令窗口按Enter鍵就可以運行了,如圖30所示。

Go語言魅力初體驗

圖30

2.4 配置Linux系統下的Go語言開發環境

Go語言環境在Linux操作系統下安裝也是非常方便、非常簡單的。首先我們需要準備一臺可以實現Golang環境的雲服務器、物理服務器或者本地的虛擬機。本節所演示的操作系統是基於CentOS/RedHat 7.X版本進行講解的。本節使用的是阿里雲的雲服務器。首先我們無論使用何種形式,都需要使用我們的遠程終端連接到我們的遠端服務器上。

首先我們來安裝一下Go語言的二進制包,下載地址:https://golang.google.cn/dl/。下載go1.11.2.linux-amd64.tar.gz該二進制軟件包到遠端服務器上,下載後並進行解壓操作,命令如下所示。

[root@golang src]# wget https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz

[root@golang src]# ls

go1.11.2.linux-amd64.tar.gz

[root@golang src]# tar xf go1.11.2.linux-amd64.tar.gz

[root@golang src]# ls

go go1.11.2.linux-amd64.tar.gz

運行go version命令查看go版本時提示報錯-bash: go: command not found,這是因為沒有設置系統環境變量造成的,只需在家目錄下面的.bash_profile文件中添加如下所示代碼即可。

[root@golang ~]# vim .bash_profile
#根目錄
export GOROOT=/usr/local/go
#bin目錄
export GOBIN=$GOROOT/bin
#工作目錄

export GOPATH=/data/www/golang
export PATH=$PATH:$GOPATH:$GOBIN:$GOROOT

設置完系統環境變量後在使用go version命令查看golang的版本已經是可以了,命令如下所示。

[root@golang ~]# go version
go version go1.11.2 linux/amd64

輸入go env也可以查看Go語言的一些系統相關的配置信息,命令如下所示。

[root@golang ~]# go env
GOARCH="amd64"
GOBIN="/usr/local/go/bin"
GOCACHE="/root/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/data/www/golang"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build717386972=/tmp/go-build -gno-record-gcc-switches"

現在,我們來一起運行一個測試示例測試一下是否安裝正常,代碼如下所示。

[root@golang www]# cd
[root@golang ~]# cd /data/www/humingzhe.com/
[root@golang humingzhe.com]# vim hello.go
Press ENTER or type command to continue
package main

import "fmt"

func main() {
fmt.Println("Hello World")
}
[root@golang humingzhe.com]# go build hello.go
[root@golang humingzhe.com]# ./hello
Hello World

2.5 配置Linux系統下Go語言IDE Goland

待續

2.6 配置Mac系統下安裝Golang開發環境

待續

3. 在Github上創建Hello World

在前面的章節中,我給同學們講解了Golang的特性,Golang在各種系統環境下的IDE的使用以及簡單的寫了一個Hello World的小例子。通過簡單的Hello World簡單了介紹了下Golang。下面我們來介紹下Hello World中每段代碼的含義,代碼如下所示。

package main 
import "fmt"

func main() {
fmt.Print("Hello World")
}

第一行代碼package main:表示main包名。golang中它有一個特性,它是用package來組織源代碼中各種各樣的東西的。你可以去把很多代碼寫到不同的源文件中,但是你如果聲明成同一個package就相當於是一個package裡面的東西,和Java中的Class是很相似的一個概念。

第二行代碼import "fmt":表示引用包的格式。golang不像其他語言一樣,它是通過直接寫一個fmt.xxx來表示的,而且必須用引號引起來,它是當成字符串一樣去引用。Golang的奇葩之處在於它的引用是用絕對路徑引用,後續的章節中會涉及到這個問題。假設引用github上的一個包,必須要引用import "github.com/humingzhe/golang"。

第三行代碼 func main():Golang所有的開源項目,無論是大項目還是小項目都要有main函數,main函數是從C語言中繼承過來的。main函數作為程序的入口點,它編譯後程序運行時會找main函數編譯出來的地方去從頭到尾開始往下運行。main函數的名字不能亂用。

第四行代碼fmt.Println(""):Golang裡面其實也帶了一個最簡單的print("")函數,但是我並不建議大家使用,Go官方也不建議使用print("")函數。這個print("")函數是用來做golang初始版本go的debug用的。如果你想print什麼東西可以使用fmt包裡的Println。fmt是format的簡寫,通過符號點接上Println。它的函數P必須是大寫的,ln表示換行,在括號裡面加上字符串就可以了。如果不加ln的話是不會換行的,不妨我們來測試一下,測試結果如圖31所示。

Go語言魅力初體驗

圖31

第五行代碼:Println表示打印Hello World並換行,Println和print不太一樣,Println默認是打到標準輸出上的,print是打到標準錯誤上的。為什麼要有print這麼一個東西?假設Println在實現的過程中,比如我實現Println,我想報個錯怎麼辦?或者說我不想依賴這個包做些底層的東西它是通過print來實現的。print可能會在後續的版本中移除掉。所以禁止大家使用。講解這個print只是告訴大家看到print是幹什麼用的就可以了。

Println有很多的兄弟姐妹,通過fmt.就可以看到很多兄弟姐妹。如果你熟悉C/C++編程會發現基本上就是系統調用的直接翻譯,如圖32所示。

Go語言魅力初體驗

圖32

下面簡單介紹下里面的幾個函數,Print家族有Println和Printf,前面我們講解過了Println,現在來介紹下Printf。Printf("")表示支持一個format string,什麼是format spring呢?就是先指定格式,比如先指定%s,比如%saaa%s。然後在指定需要打印什麼,比如打印"hello","golang"。它會打印出來helloaaagolang,代碼如下所示。

package main
import "fmt"
func main() {
fmt.Printf("%saaa%s", "hello", "golang")
}

%s表示做替換,基本上是所有語言都支持的一個特性,為什麼說它是所有語言都支持的的特性呢?因為這是世界標準組織ANSI規定的標準。它是一個比語言更高級的東西,所以說很多語言都支持它。%s表示字符串,%d表示數字,不妨我們來測試下%d,代碼如下所示。

package main
import "fmt"
func main() {
fmt.Printf("%daaa%s", 123, "golang \\n")
fmt.Printf("%d111%d", 123, 345)
}

打印的結果同學們可以先想一下會是什麼,是不是123aaagolang和123111345兩行結果呢?如圖1-33所示和我前面提到的結果完全一致。

Go語言魅力初體驗

圖33

Go有兩個非常重要的環境變量,GOROOT和GOPATH,GOROOT環境變量指向的是GO的安裝路徑,如圖34所示。

Go語言魅力初體驗

圖34

ll /usr/local/go/是安裝go包的目錄,一般看目標路徑都是指向到bin目錄。bin目錄裡面有go的運行程序。把GOROOT裡面的bin目錄加入到PATH(系統環境變量)中。GOPATH是以後源代碼放到這個目錄中。

ls -l $GOPATH中的bin裡面的東西相當於是用go get安裝的一些東西會放到這裡面,pkg是用go get去get一些pkg放到這裡面,src是我們自己開發的源代碼的存放目錄。比如src裡面有很多的目錄,比如這個github.com目錄中存放的就是github上面的一些東西,而wangzhiqiang這個目錄中存放的是我自己的一些項目源代碼。

我們還要做一件非常重要的事情,那就是在github上面去創建一個項目,因為我們以後開發的項目都會放到github上去託管。現在帶著大家來體驗下github。Go語言它天然的和代碼管理倉庫git有著非常密切的一種關係的。所以說,大家如果沒有github賬號的趕緊去註冊一個github賬號。有github賬號的,就可以進行登陸了。登陸後把github的賬號名字(username)發給我,這個賬號名字並不是註冊登陸的郵箱,而是你的ID。把你的ID發給我,我去建一個group把同學們都加進來。

我創建好group後,同學們如何把我在github上創建的那個目錄copy下來呢?可以使用go get命令進行下載,命令如下所示。

[root@golang ~]# go get -u github.com/HuiMingTenchnology/golang-homework

go get下來之後我們進入到GOPATH目錄裡面的src目錄下面的github.com目錄下面就可以找到我們剛下載的HuiMing Technology目錄,命令如下所示。

[root@golang ~]# cd $GOPATH
[root@golang golang]# ls
bin pkg src
[root@golang golang]# cd src/github.com/HuiMingTenchnology/golang-homework/
[root@golang golang-homework]# ls
LICENSE README.md

現在,我們就把github中的這個組織checkout進來了。注意,我們項目的目錄路徑一定要搞成這個樣子,千萬不要把golang-homework/ check out到別的目錄裡面。前面的github.com/HuiMingTenchnology/目錄千萬不要省,省了就會出現問題。我覺得這是Golang裡面最奇葩的一個地方,就是它必須要跟github或者repo上的路徑保持一致,公司內部repo也是一樣的,就是要把URL體現到路徑裡面。

項目check out下來後,就可以把hello world這段代碼push到github的項目中了。如果你只是看的話,同學們可以只用這一個repo,如果後期做別的項目呢,可以去建自己的目錄,在自己的目錄中去做項目就可以了。當然還是用這一個repo。

在golang-homework這個目錄裡面增加一些內容。比如增加一個hello.go的文件,添加如下所示代碼。

[root@golang golang-homework]# vim hello.go
package main

import "fmt"
func main(){
fmt.Println("Hello World")
}

執行git add命令,將hello.go從工作區添加到暫存區中,命令如下所示。

[root@golang golang-homework]# git add hello.go 

將暫存區裡的改動文件添加到本地的版本庫,命令如下所示。

[root@golang golang-homework]# git commit -m "add hello.go"
[master 46ef2c5] add hello.go
1 file changed, 7 insertions(+)
create mode 100644 hello.go

最後push到github中,命令如下所示。

[root@golang golang-homework]# git push -u origin master
Username for 'https://github.com':

提示讓我們輸入username,什麼意思呢?。默認使用go get它有一個好處,就是它會自動目錄建起來。但是有個壞處就是默認會通過https這種只讀方式幫你把repo給check out下來。這時我們其實不想這樣做,因為我們想編輯它,想用SSH這種形式。我們可以編輯.git目錄下的config配置文件進行更改,把裡面的url改成github中的git clone 以.git結尾的路徑。修改如下所示。

[root@golang golang-homework]# vim .git/config
[core]
repositoryformatversion = 0
filemode = true

bare = false
logallrefupdates = true
[remote "origin"]
url = [email protected]:HuiMingTechnology/golang-homework.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

我們再次執行git push命令就可以了,命令如下所示。

[root@golang golang-homework]# git push -u origin master 

打開github我們可以看到,在我們這個golang-homework項目中已經存在這個hello.go文件了,如圖35所示。

Go語言魅力初體驗

圖35

編寫HTTP/TCP版Hello World

先來看下下面這段代碼。下面這個版本是http版的一個Hello World的寫法。任何外部的庫都沒有使用,就是通過這麼幾行代碼就可以實現一個簡單的HTTP服務器。它可以訪問根目錄,訪問Hello World。而且這個HTTP服務器寫出來之後性能會比你想象中要強很多。具體強在哪裡呢?先彆著急,後面我會帶著大家去做一個簡單的性能測試。

package main

import (
"fmt"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}

現在我們使用goland ide把我們前面章節中創建的github項目克隆到goland ide中,在goland ide中找到文件選擇settings打開,選擇gopath,設置我們的gopath路徑,如圖36所示。

Go語言魅力初體驗

圖36

將github項目存放到F盤的golang目錄下的src目錄中,若沒有src目錄則需要自行創建。創建完畢後,我們找到下面的Git,配置下我們的git,配置如圖37所示。

Go語言魅力初體驗

圖37

當然,github需要進行登錄才可以使用。不然是拉取不下來源碼的。當上面配置完畢後,我們把設置界面關閉掉,找到goland ide頂部菜單欄中的VSC,選擇Check out from Version Control中的Git進行配置,設置完畢後單擊右側的Test按鈕進行測試,測試無誤後就可以點擊下面的Clone按鈕進行克隆了,如圖38所示。

Go語言魅力初體驗

圖38

克隆成功後,就可以在goland ide中顯示github中的項目了,如圖39所示。

Go語言魅力初體驗

圖39

在根目錄下新建一個叫做http-hello的go文件。http-hello.go文件新建成功後它會自動幫我們寫入package main這行代碼。它和我們之前寫的hello.go文件有些不同,首先第一行代碼都是package main,必須要有這麼一個main的包。然後需要import導入包,但是http-hello.go文件中我們需要import兩個包,可以寫兩行,比如import net/http,import fmt。當然也可以進行簡寫。就是import用()括起來在()裡面寫"fmt"和"net/http",最後把這兩個引用進來就可以了。代碼如下所示。

package main

import (
"fmt"
"net/http"
)

在Go語言中寫一個簡單的http server它是一個非常範式化的東西。什麼意思呢?首先我們要有一個main函數,main函數是一個入口。然後直接用http包裡面的handler function註冊一下,就是說我訪問httpserver的根目錄我就用這個handler去執行,這個handler的格式也是寫死的規定的一種格式。就是要有一個handler,第一個參數是w http.ResponseWriter 第二個參數是r *http.Request。下面是把你要輸出的內容寫到w裡面。這個w的類型是http.ResponseWriter。在這裡面Go有一個特點,只跟Java或者C相比較的話,他的特點是這個參數的類型放到參數後面去寫。我只是覺得這個事情挺怪異的,但是人家這麼設計咱也是沒有辦法的。放後面就是說明我這個參數叫w,類型必須是http.ResponseWriter。r是http.Request,把這個函數做一個參數,在Go裡面不區分函數和函數指針,它是一樣的東西。然後就把這個handler直接就註冊到這個上面,然後在直接執行http.ListenAndServe,然後傳個nil就可以了,這是一些參數相關的東西,這個裡頭我們不在用它。這就是告訴他我們監聽這個機器所有的Interface的8080端口就可以了。這個時候去訪問這臺機器的8080端口,他就會輸出一個Hello World,代碼如下所示。

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello %s!", r.URL.Path)
}

func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}

它是一個什麼流程呢?這裡面ListenAndServe相當於啟一個服務器,啟一個服務器之後,這個服務器就會根據你註冊的這些回調函數,當一個請求過來之後,它就會主動的把這些ResponseWriter這個w給你傳進來,Request也給你傳進來。你可以根據request進行一個解析,看她請求的是什麼東西或者參數是什麼都可以在這裡面獲取到。獲取到之後,你所需要乾的唯一一件事就是往w裡面寫一點東西就可以了。

Fprintf和前面說到的printf是類似的。只是Fprintf是接收一個writer的一個參數。就是把Hello World這個字符串寫到writer裡面就可以了,以上就是一個完整的HTTP服務器的執行處理過程。

運行一下這個HTTP服務器,在goland ide中右鍵點擊run,如圖40所示。

Go語言魅力初體驗

圖40

打開瀏覽器,在地址欄中輸入本地IP地址127.0.0.1或者localhost和HTTP服務器的8080端口即可進行訪問,如圖41所示。

Go語言魅力初體驗

圖41

登陸遠端服務器,在遠端服務器上git pull下來github上我們剛剛提交的http-hello.go文件,命令如下所示

[root@golang golang-homework]# git pull
[root@golang golang-homework]# ls
hello.go http-hello.go LICENSE README.md

使用go build命令去build http-hello.go這個文件,build成功後使用ls命令可以看到多出來一個http-hello的文件。把http-hello文件運行起來,命令如下所示。

[root@golang golang-homework]# go build http-hello.go

[root@golang golang-homework]# ls

hello.go http-hello http-hello.go LICENSE README.md

[root@golang golang-homework]# ./http-hello

現在來做一下壓力測試。使用siege命令進行壓力測試。在壓測http-hello之前我們先壓一下Python版的看能壓到多少,命令如下所示。

[root@golang golang-homework]# python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

重新打開一個終端窗口,使用siege命令進行壓力測試,命令如下所示。

[root@golang ~]# siege -c 200 -t 5s http://127.0.0.1:8000/

壓出來的結果是2216.53 trans/sec,如圖42所示。

Go語言魅力初體驗

圖42

接下來,我們再來測試下Go版本的http-hello。在第一個終端窗口執行./http-hello,然後繼續在第二個終端窗口執行壓測命令,命令如下所示。壓測結果如圖43所示。

[root@golang ~]# siege -c 200 -t 5s http://127.0.0.1:8080/
Go語言魅力初體驗

圖43

我們可以從上面看出來,Go還是要比Python性能強大很多,一個在3000左右一個在13000左右。基本上併發提高,Transaction rate就可以提高。其實瓶頸應該在siege這邊,你只要把併發提高,他的qbs就會上升,這就可以說明Go的性能完全不是問題。

http-hello這個文件大概是6M左右的大小,命令如下所示。

[root@golang golang-homework]# ll http-hello -h
-rwxr-xr-x 1 root root 6.3M Dec 4 21:40 http-hello

就算是一個最簡單的hello.go文件也會有1.9M,為什麼會這麼大呢?其實和Java比起來也不算大。但是和C編出來的比較可能顯得比較大。它這裡面相當於是把Go的很多的庫或者其它的一些東西都加進去了。包括裡面和協程相關的各種各樣的東西,所以說編出來一個最小的文件大概也要1.9M左右。

如果用C去寫一個帶HTTP服務的東西其實也差不多啦,也要拉一堆庫進來,但是Go全都內置了,大概就是這樣的一個情況。

我們還可以繼續把http-hello.go文件中的內容進行改造,代碼如下所示。

package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello %s!", r.URL.Path)
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}

在func handler裡面傳了一個參數r,而且我們這是Fprintf。注意分析,這個函數命名其實是一個非常通用的,其它的語言都會有這種函數。這個大寫的F表示file的意思。也就是它往一個句柄裡面打印,然後print,後面的小f表示format的意思。也就是往一個句柄裡面按一定格式去打印。這個格式就是用這個格式,然後%s,就是把urlpath打印出來。什麼意思呢?就是我在url裡面打點什麼東西網頁上就會出什麼東西。如圖44所示。

Go語言魅力初體驗

圖44

但是我在url中寫入aaa=?1u2ieas,?1u2ieas他就不會進行打印,因為這是屬於參數的。如圖45所示。

Go語言魅力初體驗

圖45

但是當我們去掉r.URL後面的.Path時再次刷新頁面就可以進行訪問了,如圖46所示。

Go語言魅力初體驗

圖46

這個URL裡面應該是有一個string函數什麼的,他就可以把後面的東西全部加進來。

我們可以改造一個自己的程序,我們的目標是什麼呢?我們通過這個東西打出來請求機器的IP。我沒看,但是我知道這個r.裡面應該可以.出來有一個東西就是表示他請求的端的IP的。然後我們現在的任務就是http-hello這個程序改成請求打印hello這個ip。大家發揮一下聰明才智看看怎麼可以搞出來。

其實r.RemoteAddr這個參數就是,如圖47所示。只不過有時也會打印出來ipv6的格式,如圖48所示。

Go語言魅力初體驗

圖47

Go語言魅力初體驗

圖48

當然,我們還可以繼續修改,比如想有不同的業務邏輯或者等其它的內容,都可以用不同的handler去處理,代碼如下所示。

package main

import (
"fmt"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello %s!", r.URL)
}

func admin_handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "admin %s!", r.URL)
}

func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/admin", admin_handler)
http.ListenAndServe(":8080", nil)
}

我們當訪問localhost:8080的時候默認訪問的是根目錄下的Hello,但是當我們在/後面加上admin的時候訪問的則是/admin頁面下的內容admin,這相當於是一種匹配的邏輯,匹配到什麼就是什麼。如果大家懂Web開發的話相信會都會明白和理解這個道理,比如我們訪問/admin,就會在瀏覽器中打印出來admin /admin,結果如圖49所示。

Go語言魅力初體驗

圖49

不妨在/admin後面再加上一些內容,比如/admin/aaaabbshdhas,如圖50所示。

Go語言魅力初體驗

圖50

同學們發現沒發現,他居然沒往/admin上面去匹配,而且匹配到的/目錄下,這是為什麼呢?其實這是因為我們的代碼/admin後面沒有加上/造成的,我們在/admin字符串後面加上/即可,代碼如下所示。

func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/admin/", admin_handler)

http.ListenAndServe(":8080", nil)
}

此時再去訪問瀏覽器,則會發現它顯示的是admin/admin/ aaaabbshdhas,如圖51所示。

Go語言魅力初體驗

圖51

但是在將/admin/改成/admins/它就會像圖50一樣,訪問的還是根目錄下的hello,如圖52所示。

Go語言魅力初體驗

圖52

它會按照匹配順序先匹配長的在匹配短的,也就是先匹配/admin/在匹配/的一個結果。

這個來說也沒什麼特別的,Go語言它還是挺簡單的,比如說連接各數據庫,直接RESTFUL API就可以寫了。而且簡單到就連編譯出來部署也是非常簡單的。不知道你拿Go寫的程序的人簡直以為你給他編譯了程序出來,更不知道你是付出了多大的辛苦,他會想是不是拿C/C++寫的呀?那可能得寫好幾千行代碼才能實現。而Go語言就純傻瓜化了。

上面是一種最簡單的,這裡有一種辦法來實現,新建一個叫做time-server.go的文件粘貼如下所示代碼。下面的代碼相當於是不用HTTP包,直接用TCP的方式來實現一個TCP的一個很底層的Sever。就是當我連上這個東西后,往這個連接裡面去打一下當前的時間,在把這個連接關閉掉。

package main

import (
"fmt"
"log"
"net"
"time"
)

func handle(conn net.Conn) {
fmt.Fprintf(conn, "%s", time.Now().String())
conn.Close()
}

func main() {
l, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
go handle(conn)
}
}

現在用8080端口去弄個了一個服務,雖然可以使用瀏覽器進行訪問,但是它其實是一個TCP版的。最好是用telnet去訪問比較好。

使用go run命令在Linux服務器上運行time-server.go文件,命令如下所示。當終端窗口的的光標停留在go run time-server.go下方不動時表示該程序已經處於運行狀態。

[root@golang golang-homework]# go run time-server.go

重新打開一個遠程終端窗口,使用Telnet命令ping本機IP的8080端口,這個Server的目標就是連接之後不用說話,這個TCP就說我不說東西,它直接給我回一個時間。然後就把我斷開了,如圖53所示。

Go語言魅力初體驗

圖53

time-server.go裡面的代碼是一個典型的TCP服務的寫法。同學們應該都明白TCP和HTTP之間的關係。就是我可以在TCP上寫HTTP的協議就能實現一個HTTP的服務器。同理,HTTP是運行在TCP上面的。

現在帶領同學們來解釋下time-server.go文件中的每行代碼的作用,第一行package main和下面的import ""此處不再進行講解,前面已經講解的非常深入了。我們直接從func處進行講解,代碼如下所示。

func handle(conn net.Conn) {
fmt.Fprintf(conn, "%s", time.Now().String())
conn.Close()
}
func main() {
l, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
go handle(conn)
}
}

func handle表示聲明一個handle函數,這個handle也比較簡單,它傳參只有一個conn,它相當於是往conn裡面打一個time.Now().String()。就是取當前的時間轉換成String然後傳過來,直接往這個(conn)鏈接裡面寫,寫完之後立馬關閉掉這個連接(conn.Close)。

下面是聲明瞭一個main函數,首先拿net.Listen去監聽TCP的8080端口。然後判斷一下是否有誤,有錯誤就打印log.Fatal(err)。然後進入for循環,用L(Listen Socket)進行Accept(接收連接過來的連接),接收到連接(conn)後先判斷一下有沒有錯,有錯就打日誌,沒有錯就把這個連接當成一個參數直接傳到handle函數里面。

handle函數前面如果不加go也是可以的,就是相當於直接調用這個函數,它意思就是說我去調用一次這個函數。然後等這個函數返回了,我就讓for循環結束。

上面示例代碼中for循環的寫法是直接一個for 然後什麼都不加,這在golang裡面表示無限循環的意思。和其他一些編程語言中的while ture是一個意思。

如果Accept異常呢?Accept的異常都是通過err(error)返回的,Golang裡面不太推崇用異常來處理,都是通過返回值進行處理。Golang裡面很多調用都是返回兩個元素,比如像main函數中的L和err(error),for循環中的conn和一個err。很多都是這樣的一種結構。就是一種東西返回兩個。

但是最下面加了一個go就完全不一樣了,什麼意思?這個意思就是說,我去開一個協程,這個協程去單獨運行這個函數handle,然後在這個協程函數里面去進行一個處理。

那麼問題就來了,什麼是協程呢?這個來說可能會偏理論一些。但這也是繞不開的。如果大家不清楚什麼是協程,那麼大家應該是清楚什麼是進程的吧。除了進程還有線程和協程。總共是這麼三個東西。這三個東西呢它相當於是一個層面的東西,是操作系統用來管理任務的一種方式。

Golang併發原理和C10K問題

進程很好理解,比如說去敲一個命令,或者去運行一個Server,這些Server和命令本身就是一個進程。這些進程裡面可能會開很多個線程,然後進行一些操作。開線程的含義主要在於能充分利用多核,也可以把一些任務進行分工。協程是一種更加輕量級的東西,它比線程更要輕量級,輪重量來說,進程>線程>協程。論歷史出現的順序來講,最先出現的是進程,這個毋庸置疑,為什麼最先出現的是進程呢?因為稍微有點像樣的操作系統都是多任務操作系統。既然是多任務,那你很多個任務之間如何去區分彼此呢?他就是靠進程,不同的進程去區分。進程完了出現的不是線程,而是協程,最後才出現的線程。它是這麼一個歷史過程。

協程具體又是個什麼樣的東西呢?我們說這個東西就不得不引入一個概念(話題)。我們從一個非常直觀的角度上,我們運行一個Linux命令,或者寫一個什麼樣的程序,我們在Linux裡面有這麼一個直觀的 感受,就是說你運行一個非常蹩腳的程序,它鎖死了,或者它把CPU打滿了,或者它吃了無數的內存,或者它佔了無數的句柄,或者說打開無數的文件,無數個無數,不管是為什麼,只要給他kill -9,它基本就可以完全消失的無影無蹤。kill -9之後,這個進程無論是多麼噁心,它佔了多少內存,多少CPU,它都如數的給你歸還到你的手中。它不存在我這個進程被kill -9死了之後,我佔的內存和CPU還釋放不掉,這種概念是不存在的。所以,由此得出,一般教科書上會講,進程是什麼,進程是操作系統管理資源的最小單位,就是說不同的操作系統,無論是Linux、Windows還是iOS還是Android這些東西,它都有這種概念,他是一種非常廣泛的概念,這種概念用來管理這件事情的。無論哪個操作系統它都要實現出一套接口,能讓我以一種方式去把我的所有骯髒事情全放到裡面,而且操作系統要有這個能力,你在這個範圍內做出的任何事情,任何對CPU內存的一些佔用或者其他的東西,他都能一股腦的會被操作系統一直追溯著。它一旦把你幹掉之後,它能夠把你的債全部要回來。

線程就不一樣了,我們應該從直觀的感受上去講,比如說一個程序它開了一百個線程,其實如果這個程序不提供什麼樣的機制讓你能控制它有多少個線程的話,你其實根本控制不了它的什麼。人家開個線程,你也關不了這個線程。它沒有一些可以操作的機制,線程一般來說是每個程序或者每段代碼或者每個進程它去組織自己的調用的這種方式或者協作的方式的一種內部的方式。但是它由於是一種內部的方式,所以說,它和進程又有很多相同類似的點。就是說每個線程他都能去佔一個CPU,假設你有一個四核的CPU,你開一個進程,每個進程可以佔一個CPU,一個進程相當於裡面就包含了自己的一個線程。或者換一句話說,進程類似於我們是一個班,我們是Golang班,這個班作為一個整體,無論我們這個班是有一個學生還是一百個學生,它都是一個班。我們這個班結束了,畢業了,我們就全體結束了。無論你是一個人還是兩個人。但是呢,如果這個班裡面有四個學生,我就可以像分神一樣,這四個學生來雖然來說是可以共享內存,但是我可以分別佔到每個CPU上去獨立的進行一個調度。類似於一個身體多個腦袋的結構。多個腦袋可以同時運行,而且互相分工,你無論是說每個腦袋去做流水線這樣的步驟還是說就直接把任務分成四份去做,這都是你內部的事情,就是這樣的一個東西。這是線程當時被創造出來時的一個目標,目標就是充分利用多核,所以說當時大多數操作系統實現線程的時候它偷了個懶兒。特別是Linux操作系統,它偷了一個非常大的懶兒。它一想,那我進程和線程,仔細想一想,從內核實現者這個角度上想,好像沒有太大的區別。 我一個進程也能佔一個CPU,一個線程也能佔一個CPU,只是來說這一堆線程也可以叫做一個進程。既然他們從CPU佔用上是一個,那其實操作系統來說,管的最複雜的一件事就是在CPU上調度任務。這段代碼也是可以公用的,唯一的不同就是兩個進程它一定是內存是獨立的,我這個進程怎麼寫,怎麼崩潰寫,寫的再爛,我頂多會是擠佔CPU的時間,不可能是把另外一個程序搞崩潰。除非把內存擠得OM了或者擠得根本跑不動了,是有這種可能的。但是你裡面寫的再爛也干擾不了別人的程序。

進程和線程對操作系統來說,最大的一個差異就是說他們內存共享不共享,兩個進程不共享,同一進程裡的兩個線程它是共享內存的。類似於一個班級中一屋子的人負責完成一幅畫,每個人畫一個角度,畫一塊,都去畫畫。這相當於是我們共享一個畫布,或者說一塊去寫一個字,你寫一撇,我寫一捺,還是說我們分別去寫不同的字,這都是無所謂的,但是呢,只有在這一個地方上,如果說我是多個進程就類似於我給你們不同的畫布,你畫布搞成多麼噁心都和我無關。就是這樣的一個區別。所以說,你在Linux上去看,它的線程ID和進程ID它用的是同樣的空間。不知道你們有沒有這樣的體會。

什麼意思呢?在Linux服務器上使用ps aux命令可以看到如圖54所示的結果。

Go語言魅力初體驗

圖54

圖1-53

我們可以看下nginx,nginx它是一個多線程的。我們可以從圖中看到nginx它有一個進程,使用pstress命令+nginx的PID可以看nginx開了多少個線程,命令如下所示。

[root@golang ~]# pstree -a 15514
nginx
├─nginx
├─nginx
├─nginx
└─nginx

那怎麼把線程的PID打印出來呢?使用pstree命令同樣可以實現打印出來nginx線程的PID,命令如下所示。

[root@golang ~]# pstree -pa 15514
nginx,15514
├─nginx,15515
├─nginx,15516
├─nginx,15517
└─nginx,15518

15514相當於是進程,使用ps aux命令只能看到15514這個PID。但是使用pstree可以看到線程的ID。說是線程,但為什麼能看到這線程的數字感覺和兄弟姐妹似的。其實它是共享一個線程的空間,如果你手裡有Mac的話,可以去看下Mac系統下每個線程不會佔用進程PID的空間。它裡面給線程分的ID是另外的一組ID。只是存在這一點點差異。在調度上或者很多寫程序的角度上Mac(Unix)的線程和Linux的線程沒有太大的差異。所以說,很多人就會說線程跟進程沒有本質上的區別。比如我們知道在Linux系統上有個系統調用叫做forrk,一般我們開線程都是要pthread-create去調用。但是如果你瞭解Linux來說,Linux系統中只有一個API,叫做clone,它只是通過調整clone這個系統調用的不同參數去創建線程和進程,其實如果你有能力的話可以創建出介於線程和進程中間的一種東西也是可以的。我現在說的都是簡化版,其實是有一些完整的文檔裡面會和你講線程和進程它那些是共享的哪些是不共享的。比如線程之間他是共享FD的,就是說我在線程A裡面打開一個文件,它FD給我返回一個句柄是3,那我在線程B裡面直接拿3,他打開我去用這都是可以的。或者更直觀的,我多個線程都往console上去標準輸出應該是1,FD是1。我多個線程往console上打的時候其實是共享的這一個FD去往console上打。然後就是FD是共享的,內存是共享的,信號處理也是共享的。所以,你會有一個非常直觀的感受,你kill一個nginx,無論kill這些線程哪個pid,kill -9都會全死掉。

因為從操作系統這個角度,它不知道怎麼弄,因為線程是你寫程序的人控制的。你讓我kill掉它,我不知道你裡面怎麼共享的,所以說,我就只能一股腦的給你們一家子全部幹掉。就好比古代的黃帝有權利誅九族一樣,和你相關的全部給你幹掉。這是進程和線程,這是一個非常基礎的知識,然後還有很多的基礎知識,它們共享不共享,但是其實最主要的就是這些東西。然後就出現了一個問題了,早年線程創造出來是為了充分利用CPU的,但是後來被用爛了,為什麼被用爛了,就是Linux這個操作系統被開發出來的時候,我們都知道Linux的前身它是仿照著Unix在貝爾實驗室創造出來的。後來又在加州大學伯克利分校被髮揚光大。美國人嘛,他們當時沒覺得世界上有這麼多人,或者說也沒想到世界上會有這麼多人會上網,當時他們就在實驗室裡有一臺電腦,無數人擠在一臺電腦前,當時的電腦非常昂貴,不是誰都可以買得起的,不像現在一樣,人人都可以買一臺電腦。所以,他們就覺得網絡連接這種事情,一般他們也就可能困掉幾個socket這種情況,因為ssh上去一個socket,我去收個郵件一個socket,她們覺得基本上夠用了。所以說,在這裡頭因為這種假設,所以給Linux挖了無數的坑,你們想想有哪些坑?給你們提出來幾個,不知道你們有沒有印象。你們用linux肯定知道Linux的服務器上線之前要改一個東西,叫做ulimit -n。你有沒有想過,Linux的這幫設計者是傻逼嗎?為什麼設那麼低。因為它是有歷史原因的。就是Linux的各大發行版,因為歷史的原因覺得1024絕對夠用了。哪來的這麼多FD,但是事實上就是這種C10K問題,就是說一萬併發問題到了九幾年的時候才被遇到。突然發現一個服務器可能真會有一萬個人併發來訪問,然後這個事就很操蛋了。所以說ulimit -n要打開。還有非常經典的就是IP地址,IPV4,大家都知道IPV4其實當時那些人設計的時候都已經想破天了,IPV4地址空間有多少?65535*65535這麼大的空間,大概是40(4294836225)億的空間。那些人當時想,這些空間夠全人類用一輩子甚至幾輩子都用不完吧。誰曾想現在一個人不知道能佔多少個IP,一個平板,一部手機,一臺服務器,甚至手錶,電視,掃地機器人都用到IP。還有一個坑就是早期大家有沒有用過一個web服務器squid,原來是做web代理的,做緩存的一個程序,現在基本上不用了,因為這東西從性能上很爛的。當時網上流傳著一個東西,就是告訴你編squid之前,make之前你要去改/usr/local/include裡面有個limit.h頭文件裡面有個1024要改大點。內個東西的參數是什麼呢?內個東西的參數一個系統調用,用來做IO複用的select調用,select當時設計的時候它只能處理FD號小於1024的這些,後來被證實這個東西太不夠用了。所以,在後來就出現了epoll。這就是當年考慮不周的一個問題。還有一個問題就是線程這個問題當時也考慮的不周。線程為什麼考慮不周呢?因為當時覺得我一個機器當時大多都是1個CPU,每個人寫一個程序就完了,頂多你寫倆線程我覺得就是太洋氣了。後來,由於連接多了,IO的好多接口是阻塞的,阻塞就導致你寫程序時這種最簡單的邏輯,就是說我開一個線程去盯一個連接。這個連接他有可能很慢,因為CPU運算速度比網絡要快得多。但是沒有辦法,我為了應付一個連接就要開一個線程,應付一個連接開一個線程...,當時很多很多的服務器都是這種模式,apache早期,甚至Apache2也是這種模式,就是開線程就卡。還有一個叫做varnish的東西不知道大家有沒有用過,他也是一個反向代理服務,varnish是典型的靠開線程硬抗連接的。來多個連接開多少個線程。當時我線上也用到過,varnish開幾千個線程是非常常見的。開幾千個線程啊,開幾千個線程意味著什麼呢?如果你CPU非常多,16核,幾千個線程搶16個核,它會來回切,來回切,來回切。就類似於幾千個人去搶四個廁所一樣。你上完廁所出來了,我再去廁所。我上廁所出來了,他在上廁所。它因為很重,所以它只能這種廁所,而不可能是說一下進去四個人,大家對這池子一起尿,這種是線程不允許的事情。因為它的調度當時設計的非常重,他和進程用的是一樣的調度模式,所以說,為了應付這件事,就有人在很早的時候發明了一個東西叫做協程。協程這個東西它類似於操作系統的很像後門的一種東西,他長得很古怪,他像是給你一套api能讓你主動的去保存你的這個任務運行的狀態。然後你可以在去用一些東西恢復這個狀態。這樣的話,你在一個進程,甚至一個進程的一個線程裡面去模擬出多個任務,我可以先乾乾這個任務,然後把它扔到後臺保存起來然後再去幹其他的任務,然後在扔到後臺保存起來。因為你自己控制,你自己知道要保存哪些東西,所以說,他切換的力度就非常非常小,他不像CPU去調度線程那樣還需要保存,他也不知道你要幹啥,所以說,他只能把你所有東西現場保護起來,等你一會等到時間片以後在都給你load回去。協程這個東西沒有流行起來是因為這個API太難用了,迄今為止,我估計已經沒有人會不用各種各樣的庫去操作協程了,它極其複雜。後來就出現了一堆這樣的流派,比如,當時在golang出現之前,我們都知道HTTP服務器的一個最強因是NGINX,在nginx之後就沒有敢說我能超越nginx的,至少在性能上不存在,基本上nginx是無冕之王。但是我們也知道nginx他沒有線程,默認的nginx不開線程,它是會開幾個進程的,而且你也可以開一個進程,而且開一個進程的性能還倍好,也基本上可以把很多東西都秒殺了。一般線上也就開四五個或者七八個nginx就已經頂天了。當然說,他的牛逼之處不在於說我因為是一個進程去處理或者多個進程去處理就牛逼,它單進程的性能也是碉堡了。或者你們想另外一個東西redis,他更奇葩,單線程單進程,但是他的網絡性能也是基本上你們很少能碰到redis的網絡性能不夠了的。那它用的是什麼魔法呢?redis其實首先沒用線程,更沒用協程,他用的是IO多路複用。什麼意思呢?這個東西今天也就只能跟大家像扯淡一樣的一種方式,讓大家從一些很民科的角度去理解下這件事。古往今來就是網絡的編程是個非常困難,非常複雜的一個點。為什麼會很困難,很複雜呢?一個核心的原因就是說,網絡由於地理,本身它可能他會隔得很遠,網絡讀寫速度和CPU內存讀寫速度比起來簡直就是一個在地上爬,一個在天上飛。然後,問題就是說,你在天上飛的CPU可能也就是幾個CPU,比如四個CPU,地上爬的這些亂七八糟的連接可能成千上萬。甚至上十萬,上百萬。問題就是說,如果能讓這些非常少的 CPU去跟這些無數個類似於慢慢吞吞的這些socket連接去完整的進行通信去把它們完整的管理起來,又高效又不佔資源。就類似於什麼問題呢?舉個例子,這個例子很不恰當,就類似於你同時認識了五個妹子,你拿微信跟五個妹子進行聊天,你如果牛逼呢,然後就跟妹子打字也很快,其實可以五個人同時撩。我們作為一個人的經驗就是我不可能是說我撩完那個妹子說我要睡覺了再去和另外一個妹子撩。我會不斷的進行切換著撩。這個妹子發來信息之後呢,他可能有個消息提示,我現在正在和這個妹子打字的時候先不看提示,打完發送的時候立馬切到妹子看說的什麼,然後打完發送。它是這麼一個模式。而不是說我就盯著這個妹子撩,但是你想想,就是說老式的這種通過線程去處理,他就是一種很蠢的模式。就是類似於我這個人我就盯著這一個妹子撩。這個妹子一分鐘說一句話,或者半個小時它說我去洗澡了,你就在這等著。就是這麼一個過程。所以說,後來呢,像nginx,redis這種網絡服務器都借鑑這種東西去做的,就是說我一個人對你們N個人。但是這個程序寫起來就會非常的複雜,因為你想,我去寫一個人的邏輯是非常簡單的,就類似於說我等你的響應,你給我發一句話我讀完然後回覆,這是一個非常非常直觀的流程。寫程序也是非常直觀的,但是如果你是去跟N個妹子去聊,程序就會寫成這麼這樣的一種邏輯,我先去收一下這個妹子的信息,我讀一下然後我去寫完回應發送,我也不等網絡去發送,我也不等這個妹子回,我直接就切換過來。切換過來然後再去看下誰還有跟我說話,然後我再去把它回覆一下,然後在切換回來,就變成一種,不知道你們有沒有看過一個視頻。就是有一個炒肉絲、炒餅的一個哥們,五個鍋同時炒,這個鍋翻一下,這個鍋翻一下,這個鍋翻一下,然後五個並行炒,就是通過這種方式進行去炒。炒五個鍋的難度比炒一個鍋的難度是乘以5的。這個東西設計起來呢,非常困難,為什麼呢?因為你處理一個人的時候這個腦子非常簡單,你就和他聊就行了。你處理5個時候你必須首先你別把妹子的名字搞混了,別突然聊著聊著把之前跟那個人的經歷跟這個人的經歷搞串了,這樣是不行的,所以,你得有套機制,你能把你跟這個人的一些過往或者說上下文你能記住。如果你有這麼一套完整的機制,聊五百萬個妹子都不成問題。因為你切換的足夠快,只是往內存中記個上下文而已。網絡傳輸你並不知道他們是在海南還是在雲南,還是在美國的舊金山或者加拿大的多倫多。這個是說不清楚的,所以說,這種東西它寫代碼的這個邏輯或變得異常的複雜,你去看下nginx裡面有個叫event.c的文件,或者說你現在直接去看nginx代碼,完全不是可能你們想象中的那種你能知道它在幹什麼的代碼。你進去之後你就懵逼了,不知道他們程序寫的是什麼鬼。它是一種狀態機,用行話來講,叫有限狀態機編程。這個東西很難,但是所有人都會遇到同樣的問題,不只是socket編程,比如說我們遇到另外一件事,或者線上經常遇到的一件事,比如說很多公司的服務其實好多就是一個服務,無論web是php還是java寫的,後頭連接mysql,有一點你是特別特別怕的,數據庫卡住了,數據庫一旦卡住一個連接就會可能導致雪崩式的這種。因為數據庫卡住了,你這個線程完全就在等她了,你也不能接收新的請求,這個時候可能很多服務器會繼續開線程、開線程、開線程,開線程......開到一定地步之後,整個服務器就完蛋了。就會造成一種雪崩。

為什麼要說這麼一個道理呢?就是說寫程序的時候你因為事實和一些現實的原因導致你很多時候需要等一些東西完成你才能繼續走你下面的邏輯,然後你想吧這些東西的邏輯不去等的話就要去記錄他的狀態,這個東西很煩很煩,所以說就導致這種編程模式很少有人可以聽得明白,我記得我當時有一次講了三天才有那麼幾個人隱隱約約的聽明白是怎麼回事。但是寫還是照樣寫不出來,就是寫那個有限狀態機。

golang的出現他用了一種很奇葩的方式解決這個問題,golang就告訴你,你就用寫那種開線程的那種模式,一門走到黑的這種業務邏輯的方式去寫邏輯,然後你吧這個邏輯寫完之後調用一下go,吧這個邏輯go出去,他在這個語言運行層面自動去調用裡面所有的東西,這樣就做的非常牛逼了。就類似於你這個人變成影子分手術的一種感覺,我一下子就分身了。而且這個分身因為成本極低,開幾百萬個都沒問題。所以說這是go的一種模式,所以說,time-server.go文件中的go handle(conn)就是這麼一個go,這就是他開展了影分身術,我就直接開一個分身出來去處理這種東西,然後對於go handle(conn)這行所停留的時間僅僅就是開這個分身的時間,具體執行什麼東西他根本不管,反正是那個人幹去了。就是這套邏輯,所以說,你看這段代碼非常簡單,非常蠢,但是他跟nginx基本上可以比肩。go的牛逼之處就在這裡。它讓你的心智負擔會大大降低。

golang的這種模式業界叫做CSP模式,C10K問題golang就是用這種方式去解決的。就是說寫出一個能充分利用多核的程序需要很深的系統編程積澱。它是非常難的。

Golang因為一堆的特性,所以說他在很多地方用的都比較重。比如目前三個比較知名的項目,docker kubernetes和etcd等等還有很多。都是用golang寫的,不知道有哪些同學在線上跑docker kubernetes和etcd這些東西的。

go常用工具

一鍵編譯go build

go build github.com/humingzhe/golang/test

一鍵測試 go test(跑單測)

go test github.com/humingzhe/golang/test

一鍵下載所有更新依賴並編譯go get,go get能把他依賴的東西全部的下載下來。

go get github.com/humingzhe/golang/test

自動文檔工具godoc。他是拿來自己生成文檔的,就是我得有一些go的代碼,然後按照他的標準去寫,然後自動能生成這些代碼的文檔。

godoc -http=:9090

這樣就可以訪問和golang.org一樣的網站,如果不能訪問golang.org的時候就可以這樣訪問,而且建議一般平時就這樣訪問速度更快,如圖55所示。

Go語言魅力初體驗

圖55

當然,也可以模擬在golang.org的官網上下載golang的安裝包,前提是你要有梯子才行。

查看在線文檔

godoc.org/github.com/golang/protobuf/proto

也可以通過godoc.org/github.com/golang/protobuf/proto這個網址去查看在線的文檔。

git概念和操作


分享到:


相關文章: