GO語言特點

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

抽時間看看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。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.