這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
抽時間看看Google的GO語言到底有什麼特點。Go說得是不錯,自從C依賴,N年沒有一個經典的程式設計語言了,電腦發展了幾十年,語言還是C的那一套,是該有所作為了,做起來真的不容易啊。看看GO到底有哪些地方做的很好。
編譯打包
python很好,只是依賴於python環境,譬如CentOS5.5上是Python2.5,還沒有json。。。
如果在CentOS6上開發的.py,直接放到CentOS5.5,有可能是跑不起來的,這個對於商業化部署還是很頭疼的。
一種方式是把Python2.6虛擬機器編譯出來,還可以用cxfreeze和pyinstaller打包成一個binary,不再依賴於python環境。
一般都是選擇後一種了,一般編譯出來的檔案幾兆左右,和用c/c++編譯出來的程式沒有什麼區別。
額,來看看GO,GO其實不是解釋性的語言,而是靜態語言。所以是可以編譯的:
// hello.gopackage mainimport "fmt"func main() { fmt.Printf("hello, world!\n")}
編譯一下:
[winlin@dev6 go-rtmp]$ go build hello.go [winlin@dev6 go-rtmp]$ ls -lh-rwxrwxr-x 1 winlin winlin 2.2M Jan 13 22:31 hello
查看依賴,只依賴於libc:
[winlin@dev6 bin]$ ldd gotour linux-vdso.so.1 => (0x00007fff263ff000)libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003856600000)libc.so.6 => /lib64/libc.so.6 (0x0000003855a00000)/lib64/ld-linux-x86-64.so.2 (0x0000003855200000)
關於GOPATH,其實類似於python的site-packages,譬如:
go get code.google.com/p/go-tour/gotour
這條命令會在$GOPATH下載go-tour以及依賴的包,然後編譯出gotour執行檔案,直接$GOPATH/bin/gotour執行就可以。
go get相當於執行下面的命令:
這個命令會執行: mkdir -p $GOPATH/src && cd $GOPATH/src mkdir -p code.google.com/p && cd code.google.com/p hg clone https://code.google.com/p/go-tour然後下載依賴的項目: cd $GOPATH/src/code.google.com/p hg clone https://code.google.com/p/go.tools hg clone https://code.google.com/p/go.net然後開始編譯: mkdir -p $GOPATH/bin && cd $GOPATH/bin go build code.google.com/p/go-tour/gotour其實go get最後一步調用的不是build,而是install: mkdir -p $GOPATH/bin && cd $GOPATH/bin go install code.google.com/p/go-tour/gotourinstall就會產生pkg。GOPATH就是用來指定這個dir的,可以在任何目錄調用go install,會產生到GOPATH這個目錄。
go install安裝某個package時,要求package的目錄結構有規則,可以查看go help gopath。
一般而言,可以用兩個GOPATH,一個用來裝哪些個依賴包,一個是自己的包。參考:https://code.google.com/p/go-wiki/wiki/GOPATH#Repository_Integration_and_Creating_
譬如,在/etc/profile中設定如下:
# for google go. export GOROOT=/usr/local/go export GOPATH=/home/winlin/git/google-go:/home/winlin/git/go-rtmp export PATH=$PATH:$GOROOT/bin
執行:go get code.google.com/p/go-tour/gotour
會產生如下項目:
[winlin@centos6x86 ~]$ ls /home/winlin/git/google-go/src/code.google.com/p/go.net go.tools go-tour
外部依賴庫就安裝到了第一個GOPATH所在的目錄了。
在自己的目錄下建立package,譬如:mkdir -p /home/winlin/git/go-rtmp/src/hello
然後:vim /home/winlin/git/go-rtmp/src/hello/hello.go
輸入以下內容:
package mainimport "fmt"import "math"func main() { fmt.Println(math.Pi)}
在任意位置都都可以編譯這個package:
[winlin@centos6x86 ~]$ go build hello[winlin@centos6x86 ~]$ pwd/home/winlin[winlin@centos6x86 ~]$ ls -lh hello -rwxrwxr-x. 1 winlin winlin 1.8M Jan 14 21:50 hello[winlin@centos6x86 ~]$ ./hello 3.141592653589793
實際上如果編譯一個錯誤的package,會顯示go尋找的目錄位置:
[winlin@centos6x86 ~]$ go build hellscan't load package: package hells: cannot find package "hells" in any of:/usr/local/go/src/pkg/hells (from $GOROOT)/home/winlin/git/google-go/src/hells (from $GOPATH)/home/winlin/git/go-rtmp/src/hells
可見是先去GOROOT找,然後去所有的GOPATH找。
總之,GO在編譯打包上沒有問題。
Package依賴關係
GO的核心目標是大規模編程,所以在處理依賴方面必須要很強悍。即使用者不需要處理任何編譯的依賴關係,go自動處理,只需要遵守語言的package規範即可。
這一點還是真的很贊,要知道編譯一個ffmpeg真的不容易,依賴巨多,版本居多,編譯錯誤後可能得找出錯的那個庫的依賴,以此類推,確實是一件不容易的事情。
GO如何處理這個問題?看看如何編譯SRS,分別是c++的和GO的兩個版本。
參考:http://dev:6060/doc/code.html
C++版本,參考:https://github.com/winlinvip/simple-rtmp-server
git clone https://github.com/winlinvip/simple-rtmp-server cd simple-rtmp-server/trunk./configure --with-ssl --with-hls --with-ffmpeg --with-httpmake
其實還好?其實不然,如果在CentOS6下面編譯,基本上沒有問題,如果換個環境呢?肯定編譯失敗。原因是configrure做了很多事情,需要安裝gcc/g++/make等工具,需要編譯nginx/ffmpeg,編譯nginx需要安裝pcre,安裝ffmpeg時需要libaacplus/liblame/libx264,以此類推,真的是不容易的一個指令碼。
具體的依賴項目,得用一個wiki才能搞定:https://github.com/winlinvip/simple-rtmp-server/wiki/Build
GO版本,參考:https://github.com/winlinvip/go.srs
export GOPATH=~/mygogo get github.com/winlinvip/go.srs/go_srs
這樣就可以?是的,這樣就可以了。
C++的srs編譯在:./objs/srs
GO的srs編譯在:$GOPATH/bin/go_srs
其實go做了很多事情,因為go.srs依賴的其他package都是按照go的規範寫的,所以go get命令可以自動下載需要的依賴包,並且進行編譯。
查看GOPATH就知道它做的事情:
[winlin@centos6x86 ~]$ tree $GOPATH/home/winlin/mygo├── bin│ └── go_srs├── pkg│ └── linux_386│ └── github.com│ └── winlinvip│ └── go.rtmp│ └── rtmp.a└── src └── github.com └── winlinvip ├── go.rtmp │ ├── LICENSE │ ├── README.md │ └── rtmp │ └── version.go └── go.srs ├── go_srs │ └── srs.go ├── LICENSE ├── README.md └── research └── demo-func └── func_declare.go
除了go.srs,連go.srs依賴的go.rtmp也自動下載下來並且編譯了。
go get等價於下載和安裝:
go get -d github.com/winlinvip/go.srs/go_srsgo install github.com/winlinvip/go.srs/go_srs
代價就是package會比較長,好處是一個命令,搞定所有的事情,這個很贊~
並發和並行計算
無疑go的設計目標就是大規模程式,並發和並行計算是很重要也是很大的一個特點。用時髦的詞,go為雲端運算而生。從領域角度講,go是為寫伺服器/服務而設計的。
不管用什麼詞語,雲/服務都有一個重要的特點:系統為多人同時提供服務,也就是並發和並行計算。並發只同時支援多人的能力,並行計算指利用多CPU和多機器的計算系統。單進程也可以支援並發,利用linux的epoll和非阻塞非同步socket就可以做到,nginx就是典型。只是伺服器基本上都是多CPU,所以支援多進程也會有很大的優勢,nginx也是典型。
多進程編程可以參考:http://blog.csdn.net/win_lin/article/details/7755773
非同步非阻塞能帶來最高效能,麻煩的地方就是狀態機器很複雜;因此對於複雜的狀態機器,譬如RTMP協議,狀態變換巨多,用協程(協程/輕量級線程/使用者態線程)等技術就能在非同步基礎上使用同步,參考:http://blog.csdn.net/win_lin/article/details/8242653
C/C++並未提供語言層級的協程支援,而是有一些庫提供支援(python提供了yield關鍵字,但支援的不是很完善,有eventlet庫支援);go重要的特點就是在語言層級提供支援。
C/C++的庫一般只提供了協程的支援,對多進程的支援有限;go同時支援協程和多進程,go的運行時本身是多線程的。
在EffectiveGo中解釋得很詳細:http://dev:6060/doc/effective_go.html#concurrency
下面開啟了兩個協程goroutine,不斷進行累加運算:
package mainimport ("fmt""time")func main() {var fun = func (id int) {count := 0for {if (count % 1500000000) == 0 {fmt.Printf("[%v] id=%v, count=%v\n", time.Now().Format("2006-1-06 15:04:05"), id, count)}count++}}go fun(101)go fun(102)time.Sleep(300 * time.Second)}
計算結果如下:
C:/Go/bin/go.exe run R:/mygo/go.srs/research/demo/tour/go_concurrency.go[2014-2-14 11:08:02] id=101, count=0[2014-2-14 11:08:02] id=102, count=0[2014-2-14 11:08:07] id=101, count=1500000000[2014-2-14 11:08:09] id=102, count=1500000000[2014-2-14 11:08:11] id=101, count=3000000000[2014-2-14 11:08:14] id=102, count=3000000000[2014-2-14 11:08:16] id=101, count=4500000000[2014-2-14 11:08:19] id=102, count=4500000000[2014-2-14 11:08:21] id=101, count=6000000000[2014-2-14 11:08:23] id=102, count=6000000000[2014-2-14 11:08:26] id=101, count=7500000000[2014-2-14 11:08:28] id=102, count=7500000000[2014-2-14 11:08:30] id=101, count=9000000000[2014-2-14 11:08:33] id=102, count=9000000000[2014-2-14 11:08:35] id=101, count=10500000000
可見這兩個goroutine是交替執行的,go的運行時會調度它們。查看CPU,4CPU用到了25%也就是1CPU。
只需要設定一句,就可以利用多CPU多進程並行計算:
runtime.GOMAXPROCS(2)
將使用兩個CPU計算,代碼如下:
package mainimport ("fmt""time""runtime")func main() {var fun = func (id int) {count := 0for {if (count % 1500000000) == 0 {fmt.Printf("[%v] id=%v, count=%v\n", time.Now().Format("2006-1-06 15:04:05"), id, count)}count++}}if runtime.NumCPU() > 1 {runtime.GOMAXPROCS(2)}go fun(101)go fun(102)time.Sleep(300 * time.Second)}
運算結果如下:
C:/Go/bin/go.exe run R:/mygo/go.srs/research/demo/tour/go_parallelization.go[2014-2-14 11:12:22] id=101, count=0[2014-2-14 11:12:22] id=102, count=0[2014-2-14 11:12:25] id=102, count=1500000000[2014-2-14 11:12:25] id=101, count=1500000000[2014-2-14 11:12:28] id=102, count=3000000000[2014-2-14 11:12:28] id=101, count=3000000000[2014-2-14 11:12:31] id=102, count=4500000000[2014-2-14 11:12:31] id=101, count=4500000000[2014-2-14 11:12:34] id=102, count=6000000000[2014-2-14 11:12:34] id=101, count=6000000000[2014-2-14 11:12:38] id=102, count=7500000000[2014-2-14 11:12:38] id=101, count=7500000000[2014-2-14 11:12:41] id=102, count=9000000000[2014-2-14 11:12:41] id=101, count=9000000000[2014-2-14 11:12:44] id=102, count=10500000000[2014-2-14 11:12:44] id=101, count=10500000000
這兩個協程是並行運算的,4CP佔用50%即2CPU在工作。
若使用C/C++呢?需要使用庫,譬如state-threads,然後多進程需要fork,若需要通訊的話,還需要用處理序間通訊技術,著實很麻煩。
go呢?一個go關鍵字,即可支援協程和多進程,通訊用channel即可。簡單~
Reflect反射
反射是元編程概念,參考"The Laws of Reflection":http://dev:6060/blog/laws-of-reflection
簡單來講,reflect的基本類型是Type和Value,即變數的類型資訊和值資訊。
Type.Elem是擷取元素類型,譬如Type為**MyClass,Type.Elem是*MyClass,Type.Elem().Elem()是MyClass。或者說,就是類似於C/C++中*的作用,取指標的值。
Value.Elem和Type.Elem是對應的,是對值進行操作。
Value.CanSet和Value.Set是對變數進行設定作業,和C/C++一樣,只有指標才能被設定。
package mainimport ("fmt""reflect")type BlackWinlin struct {id int}type RedWinlin struct {name string}func main() {bw := BlackWinlin{id:10}var rtmp_pkt *RedWinlin = nilfmt.Println("rtmp==========================")rtmp_pkt = nilif my_rtmp_expect(&bw, &rtmp_pkt) {fmt.Println("discoveryed pkt from black:", rtmp_pkt)}fmt.Println()rtmp_pkt = nilif my_rtmp_expect(&RedWinlin{}, &rtmp_pkt) {fmt.Println("discoveryed pkt from red:", rtmp_pkt)}fmt.Println()fmt.Println("rtmp==========================")var src_black_pkt *BlackWinlin = &bwvar src_red_pkt *RedWinlin = &RedWinlin{name: "hello"}rtmp_pkt = nilif my_rtmp_expect(&src_black_pkt, &rtmp_pkt) {fmt.Println("discoveryed pkt from black:", rtmp_pkt)}fmt.Println()rtmp_pkt = nilif my_rtmp_expect(&src_red_pkt, &rtmp_pkt) {fmt.Println("discoveryed pkt from red:", rtmp_pkt)}fmt.Println()fmt.Println("rtmp==========================")// set the value which is ptr to ptrvar prtmp_pkt **RedWinlin = nilif my_rtmp_expect(&src_red_pkt, prtmp_pkt) {fmt.Println("discoveryed pkt from red(ptr):", prtmp_pkt)fmt.Println("discoveryed pkt from red(value):", *prtmp_pkt)}prtmp_pkt = &rtmp_pktif my_rtmp_expect(&src_red_pkt, prtmp_pkt) {fmt.Println("discoveryed pkt from red(ptr):", prtmp_pkt)fmt.Println("discoveryed pkt from red(value):", *prtmp_pkt)}}func my_rtmp_expect(pkt interface {}, v interface {}) (ok bool){/* func my_rtmp_expect(pkt interface {}, v interface {}){ rt := reflect.TypeOf(v) rv := reflect.ValueOf(v) // check the convertible and convert to the value or ptr value. // for example, the v like the c++ code: Msg**v pkt_rt := reflect.TypeOf(pkt) if pkt_rt.ConvertibleTo(rt){ // directly match, the pkt is like c++: Msg**pkt // set the v by: *v = *pkt rv.Elem().Set(reflect.ValueOf(pkt).Elem()) return } if pkt_rt.ConvertibleTo(rt.Elem()) { // ptr match, the pkt is like c++: Msg*pkt // set the v by: *v = pkt rv.Elem().Set(reflect.ValueOf(pkt)) return } } */ok = falsepkt_rt := reflect.TypeOf(pkt)pkt_rv := reflect.ValueOf(pkt)pkt_ptr_rt := reflect.PtrTo(pkt_rt)rt := reflect.TypeOf(v)rv := reflect.ValueOf(v)if rv.Kind() != reflect.Ptr || rv.IsNil() {fmt.Println("expect must be ptr and not nil")return}fmt.Println("type info, src:", pkt_rt, "ptr(src):", pkt_ptr_rt, ", expect:", rt)fmt.Println("value info, src:", pkt_rv, ", src.Elem():", pkt_rv.Elem(), ", expect:", rv, ", expect.Elem():", rv.Elem())fmt.Println("convertible src=>expect:", pkt_rt.ConvertibleTo(rt))fmt.Println("ptr convertible ptr(src)=>expect:", pkt_ptr_rt.ConvertibleTo(rt))fmt.Println("elem convertible src=>expect.Elem()", pkt_rt.ConvertibleTo(rt.Elem()))fmt.Println("settable src:", pkt_rv.CanSet(), ", expect:", rv.CanSet())fmt.Println("elem settable src:", pkt_rv.Elem().CanSet(), ", expect:", rv.Elem().CanSet())// check the convertible and convert to the value or ptr value.// for example, the v like the c++ code: Msg**vif rv.Elem().CanSet() {if pkt_rt.ConvertibleTo(rt){// directly match, the pkt is like c++: Msg**pkt// set the v by: *v = *pktfmt.Println("directly match, src=>expect")rv.Elem().Set(pkt_rv.Elem())ok = truereturn}if pkt_rt.ConvertibleTo(rt.Elem()) {// ptr match, the pkt is like c++: Msg*pkt// set the v by: *v = pktfmt.Println("pointer match, src=>*expect")rv.Elem().Set(pkt_rv)ok = truereturn}fmt.Println("not match, donot set expect")} else {fmt.Println("expect cannot set")}return}
GO語言的問題
GO語言RTMP伺服器:https://github.com/winlinvip/go.srs
C++語言RTMP伺服器:https://github.com/winlinvip/simple-rtmp-server
GO只有C++效能的1/30,在50個串連時,GO佔用CPU50%,C++只佔用2%。當然,應該是代碼寫的有問題。至少目前還沒有辦法轉向GO。