GO语言特点

winlinvip · · 6069 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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

package main

import "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/gotour
install就会生成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 main

import "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 hells
can'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-http
make

其实还好?其实不然,如果在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=~/mygo
go 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_srs
go 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 main

import (
	"fmt"
	"time"
)

func main() {
	var fun = func (id int) {
		count := 0
		for {
			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 main

import (
	"fmt"
	"time"
	"runtime"
)

func main() {
	var fun = func (id int) {
		count := 0
		for {
			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 main

import (
	"fmt"
	"reflect"
)

type BlackWinlin struct {
	id int
}
type RedWinlin struct {
	name string
}

func main() {
	bw := BlackWinlin{id:10}

	var rtmp_pkt *RedWinlin = nil
	fmt.Println("rtmp==========================")

	rtmp_pkt = nil
	if my_rtmp_expect(&bw, &rtmp_pkt) {
		fmt.Println("discoveryed pkt from black:", rtmp_pkt)
	}

	fmt.Println()
	rtmp_pkt = nil
	if my_rtmp_expect(&RedWinlin{}, &rtmp_pkt) {
		fmt.Println("discoveryed pkt from red:", rtmp_pkt)
	}

	fmt.Println()
	fmt.Println("rtmp==========================")
	var src_black_pkt *BlackWinlin = &bw
	var src_red_pkt *RedWinlin = &RedWinlin{name: "hello"}

	rtmp_pkt = nil
	if my_rtmp_expect(&src_black_pkt, &rtmp_pkt) {
		fmt.Println("discoveryed pkt from black:", rtmp_pkt)
	}

	fmt.Println()
	rtmp_pkt = nil
	if 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 ptr
	var prtmp_pkt **RedWinlin = nil
	if 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_pkt
	if 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 = false

	pkt_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**v
	if rv.Elem().CanSet() {
		if pkt_rt.ConvertibleTo(rt){
			// directly match, the pkt is like c++: Msg**pkt
			// set the v by: *v = *pkt
			fmt.Println("directly match, src=>expect")
			rv.Elem().Set(pkt_rv.Elem())
			ok = true
			return
		}

		if pkt_rt.ConvertibleTo(rt.Elem()) {
			// ptr match, the pkt is like c++: Msg*pkt
			// set the v by: *v = pkt
			fmt.Println("pointer match, src=>*expect")
			rv.Elem().Set(pkt_rv)
			ok = true
			return
		}
		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。


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:winlinvip

查看原文:GO语言特点

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:1006366459

6069 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传