GO语言特点

来源:互联网 发布:厦门科华工资怎样知乎 编辑:程序博客网 时间:2024/05/16 06:56

抽时间看看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。

0 0
原创粉丝点击