« 多进程资源共享及多样化加载 | 返回首页 | lua cothread »

Go 语言初步

这几天认真玩起了 Go。所谓认真玩,就是拿 Go 写点程序,前后大约两千行吧。

据说 Go 的最佳开发平台是 Mac OS ,我没有。其次应该是 Linux 。Windows 版还没全部搞定,但是也可以用了。如果你用 google 搜索,很容易去到一个叫 go-windows 的开源项目上。千万别上当,这是个废弃的项目。如果你用这个,很多库都没有,而且语法也是老的。我在 Windows 下甚至不能正确链接自己写的多个 package 。活跃的 Windows 版是 gomingw ,对于 Windows 用户,装一个 mingw32 以后就可以开始玩了。

就三天来实战经历,我喜欢上这门新语言有如下原因:

mix-in 的接口风格。非常接近于我在用 C 时惯用的面向对象风格。有语法上的支持要舒服多了。以平坦的方式编写函数,没有层次。而后用 interface 把需要的功能聚合在一起。没有继承层次,只有组合功能。

强类型系统。使得犯错误的机会大大降低。正确通过编译,几乎就没有什么 bug 了。而编写程序又有点使用 lua 这种动态语言的感觉,总之,写起来很舒服。

内置的 string / slice 类型,以及 gc 。这是我觉得现代编程必须的东西。手工管理未必有更高的效率,但一定有更多的出错机会。至少,我一直主张有一个方便的 string 不变量的基本类型的(参见这一篇)。

defer 是个有趣使用的东西,用它来实现 RAII 比 C++ 利用栈上对象的析构函数的 trick 方案让人塌实多了。go 在语言设计上是很吝啬新的关键字的。但多出一个关键字 defer ,并用内建函数 panic / recover 来解决许多看似应该用 exception 解决的问题要漂亮的多。

zero 初始化。我一直觉得 C++ 的构造函数特别多余。按我用 C 的惯例,一切数据结构都应该用 0 初始化。所以 C 里有 calloc 这个函数。go 把这点贯彻了。不会再有未定义的数据。

包系统特别的好。而且严格定义了包的初始化过程,即 init 函数。在我自己的 C 语言构建的项目中,实现了几乎一样的机制,甚至也叫 init 。但是有语言层面的支持就是好。对,只有 init 没有 exit 。正合我意。

goroutine 是个相当有用的设计。8 年前,我给 C 实现了 coroutine 库,并用在项目里,并坚信,程序就应该这么写。但是没有语言级的支持,用起来还是很麻烦。goroutine 不仅简化了许多业务逻辑的编写,而且天生就是为并发编程而生的。select/chan 可能是唯一正确的并发编程的模型。Erlang 还是太小众了,而 Go 可以延用 Erlang 的模型,却有着纯正的 C 语言血统,我想会被更多人接受的。虽然 Go 依然可以用共享状态加锁的方案,但不推荐使用。chan 用习惯了,还是相当方便的。

{ 要不要独立占一行的信仰之争终于结束了。还记得前段时间有位同学来 email 指责我开源的代码没有章法。程序写的太乱。他的理由就是,我的 { 都没有独占一行。好了,争论可以结束了。在 Go 里,如果你把 { 从 if/for 语言的行末去掉,放在下一行。编译器是不会让你通过的。(除非你再加一个 ; )我很欣慰 ;)

我发现我花了四年时间锤炼自己用 C 语言构建系统的能力,试图找到一个规范,可以更好的编写软件。结果发现只是对 Go 的模仿。缺乏语言层面的支持,只能是一个拙劣的模仿。


对于有 C 基础的同学,比如我,学习 Go 毫不费力。按这篇文章的指引即可。Rob Pike 的三日教程 PPT ,我心急,用了一个下午就看完了,并且做完了练习。

不过实战编写程序还是需要反复查阅文档的。学习一门新语言,就是在学习它的各种惯用法和库。而不是去模拟熟悉的语言。我在编写代码的时候,时刻问自己,在 Go 里,通常用什么手法来处理这个问题。接下来就是不断的查询文档了。从这个意义上讲,学习新东西还是很累的。好在 Go 的各种设计都非常切合我的本意,所以自然是越写越舒服了。

至于把变量类型申明都放在后面,按 Sean 同学的话说,有种真气逆行的感觉。对我来说倒是小问题,几个小时就习惯了。反而 C 语言那种亦前亦后的方式才是奇怪呢。


说一下我的练手项目。我用 Go 重新实现了处理多连接的服务器。当然,现在的设计方案和几年前写 blog 时的方案有略微的不同。

需求是这样的:

这个服务会监停一个端口,允许外部多个连接的接入,并可以把这个连接上的数据包汇总发到后端的一个连接上。简单的说,就是一个 N 对 1 的数据处理器。把 N 个 TCP 数据流合成一个数据流。

一个服务的处理上限是 64K 的连接,使用 2 字节的 id 号区分不同的外部连接。我定义了简单的协议,每个数据片段有 3 字节的数据头。分别是数据长度一字节和 2 字节的连接 id 号。

这个服务仅仅做数据流的合并,而不规定数据逻辑上的分包。对内的数据管道上看起来的数据流就是这样的:

len id_lo id_hi content ... len id_lo id_hi content ...  len id_lo id_hi content ... 

处理合并起来的数据流非常简单,只需要通过一个 IO 管道 (可以是 socket 也可以的 stdio/stdout ,对于 Go 来说,甚至可以是一个 in-memory Pipe )这方便后端的程序不再考虑多连接的问题。

后端服务需要可以控制连接服务器。最基本的功能就是可以强制断开某个外部连接。并且可以获得新的外部连接接入或离开的信号。

更进一步,应该由后端服务器来控制连接服务器对外监听端口的开启与关闭,以及外部连接的上限等。

为了简化设计,我选择在一个特殊的内部连接(0号连接)上收发内部的控制指令。并且使用 \r\n 分割的文本协议。


用 Go 来实现这个服务非常简洁。全部我只使用了 240 行左右的 Go 代码。所有的网络连接都使用独立的 goroutine 来控制。每个都以阻塞方式处理 socket 。主循环仅仅使用一个 select ,这类似 Erlang 的事件驱动模型。

对于控制指令流,创建一个 in-memory Pipe 即可。在对内的数据流上,过滤到 id 为 0 的数据包,转发到这个 Pipe 上。使用 bufio 把 Pipe 转换成一个 bufio.Reader 接口,就可以方便的使用 ReadString 方法去读以回车分割的文本行,进而排发到解析指令的 goroutine 中,把结构化指令利用 chan 发进消息 select 循环。整个只需要不到 10 行代码。

大多数 goroutine 内部都是一个 for 循环,结束条件是和它通讯的 chan 被外部关闭。

需要稍微考虑性能的地方是给外部连接的数据包加上三字节的头转发到内部通道上。这里如果每个包都用 make 创建一个新的 array slice 会有一些内部管理上的开销。我的做法是每次申请 16k 的 array ,再创建一个 slice 去组包,如果这个 16k 的 array 没用完,就会顺着用下去。如 Go 的教程中所言,slice 的创建是很廉价的,想来也是如此。它只是对 array 的部分引用。

Go 的引用和值分得很清楚,这使它更像 C 而不是 Java ,却又提供了 C++ 提供不了的安全性。

用 Go 写网络程序,真是非常舒心。socket 和 file 在 interface 上的统一,暗合 Unix 之道。程序嘛,就是处理输入,产生输出。Reader 和 Writer 接口让人愉快。

Comments

厉害厉害

20190501 非班科老龄程序新手 开始学习go 感觉很舒服 在流畅的python中作者就说明了go是取代c++ java python而来的 现在又在大神你这看到9年前对go的评价 啧啧 普通人表示要转go

不错

确实觉得语法上有点类pascal语言的味道。

老早就听过go语言了,学习下。

学习了。现在学golang的人好多啊。

看了《码农》杂志对您的访问,所以来看看。每篇文章都很不错,自愧不如,继续关注、学习,同勉。

欢迎来abc

小伙子不错,现在就缺Go方面的人才,赶紧来人人,上人人找同学,上同学找人人。

小伙子,代码呢,发出来看看嘛,go的资源太少了,学习资料匮乏!愿意来我公司干吗?我收留你!

那您认为Go语言更适合做那些方面的开发呢?WinForm还是Web亦或是Server?

一个服务的处理上限是 64K 的连接?

go能绑定qt吗?

听老李提起过你,小伙子不错呀,有兴趣到中科院不?有兴趣可以到中南海找我老头子

对go很感兴趣,这篇文章看了很多次,对我而言有很大帮助,我已经做了一个艰难的决定。

GO语言越来越流行了

这么多领导关注啊,不要吧技术贴变成搞笑贴哦

3楼 马云 的URL怎么跑到腾讯网站上啦?
熟归熟,可不能乱贴哈。

膜拜之~~
mark一下,语法貌似有点像python,改天抽空学一下

嗯,很好,学习了

非常欣赏你 请来我公司

小伙子,有前途, 你能不能把QT 和Go语言绑定?

楼主,这么多高管关注你,赶紧飞吧。

我们公司待遇高。

不错,到我公司来干吧?

求本文配套教学源码。。。

嗯,看过留脚印,,

gomingw可以用,用最新的版本,不要用前一个所谓stable版,那个版本被报有病毒,我不确定是否真有病毒。另外对于windows用户并不用下载mingw32,直接用命令行好像就可以。命令行参数如果不知道,可以直接用go回车,好像有一些提示,不是很难懂的。

学习了,好久没做c++,有点不习惯了都。

@马劲
http://golang.org/doc/GoCourseDay1.pdf

看GO语言的更新有点慢

里可以看出来

3Q了,最近就是想找点新东西看看。做久了没劲!

佩服,开始玩go语言了,最近连C++和jav都用不好了

语言都挺复杂的,看不懂

我刚刚做了一个很艰难的决定... ...

你们还没对一切现存的编程语言彻底失望啊?:D

我在一个虚拟机上测试了tcp socket write()和read()的开销: 10000万次write() 每次写16bytes,需要70ms; read() 10000次,每次read()读取16bytes,需要30ms, 而我ping www.163.com才10ms. 这种需要大量io的连接服务器造成的延迟还是不小啊

云风你好,问一个golang方面的问题,
在你的实现中,是采用一个client一个go-routine处理接受信息吗?那么从服务器发送给client的数据怎么处理,是另外每一个client有一个go-routine负责发送吗?还是全局只用一个go-routine负责发送?

额,我下gowin32,金山毒霸竟然报有病毒。。。

呵呵,云风你当了亚运会火炬手怎么没见你提过啊

研究下

这么大年纪了 还搞这搞那的 就是不好好做游戏 老丁对你很失望啊

goroutine 按 go 设计最初的期望,其实现和 os 的线程无关。

晕阿 怎么留言到了另外一篇文章去了。。。请教几个问题,谢谢~~:
这个连接服务器还有组播或广播的功能吗,我看您以前的文章说有组播功能?
像这种服务器如何测试是否正确呢?
用goroutine阻塞处理每个tcp连接,会不会有可能造成进程中线程过多的情况?

我也认为Go是目前最有希望的新语言,它避免了"面向类设计"的OO歧途,回归了OO的本质。

不过我觉得Go不会对C++构成冲击,它主要的竞争对手是 Java。C用来与系统层和硬件沟通,C++用来写程序 model 部分的算法,View 部分则有各种各样的新派UI工具(Qt Quick, Cocoa, HTML5, Flash, ...) 完成,留给Go的空间,要么是包打天下,要么是构造跨网络的主体框架,这两个用场都是跟Java竞争。

希望 Go 的 GC 能够尽快改进。

云风 你好“Rob Pike 的三日教程 PPT” 这个资源哪儿有呢?

用GO实现,代码行数虽然可以减少,但运行效率不一定会高吧?对于写一些底层服务程序真的适合吗?

用 C++ 实现了一遍“多连接服务器”,约 220 行代码。 https://gist.github.com/708646
只实现了基本的多路复用功能,包括后端可以获得新的外部连接接入或离开的信号,暂不支持“后端服务需要可以控制连接服务器。1. 可以强制断开某个外部连接。2. 应该由后端服务器来控制连接服务器对外监听端口的开启与关闭。3. 控制以及外部连接的上限等。”

程序嘛,就是处理输入,产生输出


that's lisp or forth

有一个很好奇的问题。大多数带GC的语言都是在VM中运行的。而Go是编译语言,直接生成的是原生的可执行代码,那么GC是如何实现的呢?是编译器自动生成了大量的内存管理代码?

不像C,像Javascript

Go,有时间去学习下。

太丢人了,原来是自己眼睛有问题了。。刚提到的那个colloc是自己敲错了...

就说没看到过colloc是啥东西,结果查了下,果然是笔误,calloc? 大侠也有错哦呵呵。

Google可能更适合云风,哪里的技术氛围可能更好.

还是第一次听说这种语言,难得收到云风如此的喜欢。有空好好看看。

总觉得语法上有点类pascal语言(比如as3)的味道

期望看到更多您对GO的介绍和心得。。。很有收获,谢谢

云风是个不折不扣的谷粉

go最喜欢的是可以为任何type加入方法, 和obj-c一样, 而不用想c++那样写死, 这样在设计模式上有很多变化.

每次看你的技术文章都有血脉沸腾的感觉,一直对go感兴趣,看到那个ppt我也想尽快看完呢。。。

云风可以加一下go的邮件列表:
http://groups.google.com/group/golang-nuts

既然也这么喜欢go,可以关注一下,很热闹,多提点意见:D

@Googol Lee

我说说我的理解。

Go 有 C 的血脉,所以依然分值和对值的引用(指针类型)

和 slice 这种东西是引用类型,但却是一个值,而不是指针。

以 C++ 做比,它更像是 smart_pointer 或 iterator 这种东西,语法上是值,但语义上是引用。

new 用来生成 *T ,make 用来生成一个 T 对另一个 T 的引用。并绑定了新的数据,比如 slice 是对 array 的引用,并绑定了边界。不同到 slice 可以引用同一个 array ,却有不同的边界。

系统级的编程语言,逃不开对实现的追究。所以我认为 new 和 make 分开是很自然的事情。

请博主 详细介绍 :
"
以平坦的方式编写函数,没有层次。而后用 interface 把需要的功能聚合在一起。没有继承层次,只有组合功能。"

对go里有new有make怎么看?我一直觉得go来强制三种类型使用make创建实例是一个很别扭的定义,而且没有想出很合理的语法解释。

go就是c+stackless python的优良结合体

其实go的优先级最高是linux,mac的问题开发组通常是放在次优解决的,这从每次的release note里可以看出来。

Post a comment

非这个主题相关的留言请到:留言本