为 Ruby 程序员准备的 Go 入门教程
2013-12-19 15:24
399 查看
这是我翻译的国外博客,如需转载请注明出处和原文链接
那些在Google的大牛们开发出了一种称为Go的牛叉的语言。乍一看,Ruby和Go有点像远房表亲。其实不然,他们那些互为补充的功能却让他们成为一对完美组合。
Ruby程序员花时间了解一下Go还是非常有好处的,因为Go语言中一些创新之举还是很不错的。
对于我来说,Go弥补了C++和Ruby之间空缺的联系。特别是当需要实现高响应的服务器的时候,我通常会选择C++,但是这样我就丢失了Ruby的精细之处。虽然我比较偏爱Ruby,可是即便是最近,当需要性能有明显提高的时候,Ruby还是应付不来。
Go弥补了这个空缺。它提供像Ruby和Python这样动态语言的感觉的同时,也提供了编译语言的性能。
Go同时有一些与众不同的特性,本文会详细介绍。好了,让我们好好瞧瞧吧。
Go提供了令人惊讶的并发基元(primitives),良好的语法,优秀的函数库和快速的编译器。它解决我在使用C(某种程度上C++也是)遇到的问题。即使是基础代码变得很大的时候,使用Go语言依然很轻松。
在这篇文章中,我会依据文档,快速的回顾一下Go语言的基础特性。我们的重点在于突出那些让Go语言与众不同的创新之举。
我们从main函数开始。好了,试着输出个“Hello,world”吧!
Go语言中输入输出模块被称作“fmt”,不像Ruby,这个“fmt”是默认不被包含的。所以需要在文件开始处用“import”声明引入。“fmt”模块中的Println函数会将你传入的字符串加上一个换行符一起输出(类似ruby的puts函数)。注意Go语言中公共方法是以大写字母开头的。
下面看一下简单的循环:
对于for循环,Go语言和Ruby完全不同。Go语言的for循环或多或少有点像C语言。你需要先定义个变量,然后检查状态,最后说明在迭代一次结束后需要做什么事(这个例子是i递增)。Go语言中的基本循环语法只有这一种。幸运的是,这个for循环非常灵活。比如说,下面这个死循环:
我希望你能查看一些有个for的文档[http://golang.org/doc/effective_go.html#for].
请注意在我们的上面的for循环中,给变量i赋值的时候,我们没有用“=”,而是使用了“:=”。这儿有个说明差异的例子:
在main函数的开始,在声明变量a的同时进行了初始化,所以使用“:="。接下来的是简单的赋值,所以使用“=”。之所以这样,是因为实际上Go语言是静态类型语言,不像Ruby这样的动态类型。因此编译器必须得知道这个变量在哪声明和在哪赋值的。最后一部分代码比较清楚,就是简单地使用var关键字声明变量,然后进行赋值。
最后,作为和Ruby中数组的一个相似点,在Go语言中的数组也有分片。下面的代码中有个[]type的类型,这个type意思是着你希望分片返回的类型。但是这样的做法有点变扭:
我们需要make()函数来获得一个分片。
如果这样继续下去的话,文章就可能成为Go语言语法的的简明教程。而我更希望将时间花费在一些有意思的新特性上,而不是这样的一个语法介绍。基本语法可以参照Go语言的文档,那会介绍得更好。
下面让我们看看goroutines吧。
本质上,goroutines是个轻量级的并发机制,通过使用一种称为channels的构建来进行线程间交互。它们都非常易于使用:
在上面的代码中,wait方法是一个死循环,但是我们通过gowait()的方式来调用,而非直接的通过wait()来调用。这是告诉Go我们希望以一个goroutine的方式来调用,同时异步运行。既然这个循环是在后台运行的,那样运行这个程序就不会因为死循环而阻塞。
这么说,Go从语言本身支持并发。也就是,Go语言中有并发基元(primitives)。这样意义何在呢?仅仅因为不是由某个库或者模块来实现并发,这好像不是什么了不起的举措啊。但是,实际上goroutine从根本上与线程不同。goroutine更加轻量化。还记得在服务器中,我们不该为每个客户端创建一个线程吧?但是,使用goroutine,情况就不同了:
噢,等会!这些代码似有那么一小点复杂啊,虽然想法是很简单。好吧,让我们一步一步慢慢来
首先,我们来看一下main函数。在main函数一开始调用了net.Listen方法,该方法会返回两个值,一个是服务器连接,另一个是错误消息。然后,进入到服务的主循环部分,在这儿程序调用server.Accept方法,然后等待请求。该方法调用后,程序会被挂起,直到有有一个客户端的连接出现。一旦有个连接出现,我们将connection对象传值到manageClient方法中,由于通过goroutine的方式调用manageClient,所以主程序会继续等待处理下一个客户端连接请求。
最后,关于这个manageClient方法要注意一下。首先,注意一下参数表,是变量名在先,类型在后。这样的格式多少是由Go语言创造者决定的。你可能甚至可能一周后都没有注意到。
在方法体中,向客户端写入“Hi!”信息,然后关闭套接字。
好了,就这么几行代码,我们轻松完成了一个基础服务器。你可以将它改成一个HTTP代理(如果加上缓存,那就更棒了)。Goroutines支持我们这么做。事实上goroutine不单单是一个轻量级的线程,因为还有许多与众不同的机制在背后在起着作用,所以才可以通过如此简练的代码的来实现goroutine功能。
程序中有个调用了sayHellothat方法的goroutine,该方法输出“Hello,world”消息。但是,注意那个eventChannel的声明。本质上,我们声明了一个整型的channel。我们可以通过这个channel来发送数据,而其他部分可以从这个channel中读取数据。这就使得channel成为了一种通信方式。在sayHello方法中,eventChannel<-1将整数1加入到eventChannel中,然后在主函数中,我们可以从eventChannel将数据读出。
这儿有一点很重要:默认情况下,如果channel中没有数据的情况下,从channel中读数据会被阻塞的,一直阻塞到可以从channel中读到数据。
来的稍微复杂的:
这里,我们完成了一个main的事件轮询,它会一直处于监听事件状态,也就是loggingLoop函数。它从loggChanne中接收到一个消息后,就会输到屏幕。这是一个非常普片的设计,特别在事件轮询中获得一些状态。
就这样,短短几行代码,我们就完成了一个main函数和goroutines之间的通信。由于共享内存的通信方式,存在着诸如互斥锁,竞态条件等问题,早已成为了开发者的噩梦。但是在Go中,channels的概念解决了多数传统问题。此外,Go的channels是语言的固有部分,而非附加在某个库中的。
与Ruby相比,Go的goroutines实际上是运行在后台,并且由语言本身实现的(MRIRuby整个运行在一个单独的线程中,所以它不能提供一个真实的并行)。此外,虽然Ruby自带线程实现,但是那实在不好使用。事实上,Agent库尝试将一些goroutines精妙的地方引入Ruby中去。
请继续关注后续的第2部分,那里我们会接触一些复杂语法,和其他一些Go语言带给我们的牛叉特性。
本文地址:http://www.oschina.net/translate/go-rubyists
原文地址:http://www.sitepoint.com/go-rubyists/
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照CC协议,如果我们的工作有侵犯到您的权益,请及时联系我们
那些在Google的大牛们开发出了一种称为Go的牛叉的语言。乍一看,Ruby和Go有点像远房表亲。其实不然,他们那些互为补充的功能却让他们成为一对完美组合。
Ruby程序员花时间了解一下Go还是非常有好处的,因为Go语言中一些创新之举还是很不错的。
对于我来说,Go弥补了C++和Ruby之间空缺的联系。特别是当需要实现高响应的服务器的时候,我通常会选择C++,但是这样我就丢失了Ruby的精细之处。虽然我比较偏爱Ruby,可是即便是最近,当需要性能有明显提高的时候,Ruby还是应付不来。
Go弥补了这个空缺。它提供像Ruby和Python这样动态语言的感觉的同时,也提供了编译语言的性能。
Go同时有一些与众不同的特性,本文会详细介绍。好了,让我们好好瞧瞧吧。
Go好在哪?
当写一个服务器的时候,一种实现并发的方式是为每个客户端开一个线程(你可能会觉得是在扯淡,好吧!没关系,继续读下去),特别是有许多客户端的时候,这种方式是非常糟糕的。较为好的解决方法是选择非阻塞IO(大家肯定表示赞同吧)。可是,即便都是Unix系的操作系统(诸如Linux,MacOSX等等),有效地处理非阻塞IO的机制也是各不相同。此外,除了这些纷繁混杂,还有个C语言。我绝不反对嵌入式设备使用C语言,因为那绝对是速度第一,开发时间第二的。但是,作为一门日常语言,C已经不能满足我的需求了。Go提供了令人惊讶的并发基元(primitives),良好的语法,优秀的函数库和快速的编译器。它解决我在使用C(某种程度上C++也是)遇到的问题。即使是基础代码变得很大的时候,使用Go语言依然很轻松。
在这篇文章中,我会依据文档,快速的回顾一下Go语言的基础特性。我们的重点在于突出那些让Go语言与众不同的创新之举。
无聊的基础介绍
Go语言是很容易上手,在基本语法这方面没玩什么新花样。下面是些基本代码:1 | packagemain |
2 |
3 | funcmain(){ |
4 |
5 | } |
1 | packagemain |
2 |
3 | import "fmt" |
4 |
5 | funcmain(){ |
6 | fmt.Println( "Hello,world!" ) |
7 | } |
下面看一下简单的循环:
01 | packagemain |
02 |
03 | import "fmt" |
04 |
05 | funcmain(){ |
06 | //thebasicforloop |
07 | for i:=1;i<100;i++{ |
08 | fmt.Println(i) |
09 | } |
10 | } |
1 | for { |
2 | } |
请注意在我们的上面的for循环中,给变量i赋值的时候,我们没有用“=”,而是使用了“:=”。这儿有个说明差异的例子:
01 | packagemain |
02 |
03 | import "fmt" |
04 |
05 | funcmain(){ |
06 | //definesthevariablea |
07 | a:=5 |
08 | fmt.Println(a) |
09 |
10 | //setsadifferentvaluetoa |
11 | a=10 |
12 | fmt.Println(a) |
13 |
14 | //anotherwaytodefineavariable |
15 | varb int |
16 | b=15 |
17 | fmt.Println(b) |
18 | } |
最后,作为和Ruby中数组的一个相似点,在Go语言中的数组也有分片。下面的代码中有个[]type的类型,这个type意思是着你希望分片返回的类型。但是这样的做法有点变扭:
1 | packagemain |
2 |
3 | funcmain{ |
4 | ///thiscreatesasliceofintegerswithlength15 |
5 | mySlice:=make([] int ,15) |
6 | } |
如果这样继续下去的话,文章就可能成为Go语言语法的的简明教程。而我更希望将时间花费在一些有意思的新特性上,而不是这样的一个语法介绍。基本语法可以参照Go语言的文档,那会介绍得更好。
下面让我们看看goroutines吧。
Goroutines
写并发的代码已经很困难了,写并发访问网络的代码就更加困难了。问题在于传统的线程不能很好得伸缩,而且线程一旦运行起来,就会很难去控制。Go语言项目组着手解决这个问题,于是乎goroutine就诞生了。本质上,goroutines是个轻量级的并发机制,通过使用一种称为channels的构建来进行线程间交互。它们都非常易于使用:
01 | packagemain |
02 |
03 | import "fmt" |
04 |
05 | funcwait(){ |
06 | //waitaroundwithaforeverloop |
07 | for { |
08 | } |
09 | } |
10 |
11 | funcmain(){ |
12 | gowait() |
13 | fmt.Println( "Wedidn'twaitbecauseitwascalledasagoroutine!" ) |
14 | } |
这么说,Go从语言本身支持并发。也就是,Go语言中有并发基元(primitives)。这样意义何在呢?仅仅因为不是由某个库或者模块来实现并发,这好像不是什么了不起的举措啊。但是,实际上goroutine从根本上与线程不同。goroutine更加轻量化。还记得在服务器中,我们不该为每个客户端创建一个线程吧?但是,使用goroutine,情况就不同了:
01 | packagemain |
02 |
03 | import( |
04 | "fmt" |
05 | "net" |
06 | ) |
07 |
08 | //noticethatinthearguments,thenameof |
09 | //thevariablecomesfirst,thencomesthe |
10 | //typeofthevariable,justlikein"var" |
11 | //declarations |
12 | funcmanageClient(connnet.Conn){ |
13 | conn.Write([]byte( "Hi!" )) |
14 | conn.Close() |
15 | //dosomethingwiththeclient |
16 | } |
17 |
18 | funcmain(){ |
19 | //wearecreatingaserverherthatlistens |
20 | //onport1337.Noticethat,similartoRuby, |
21 | //amethodcanhavetworeturnvalues(although |
22 | //inRuby,thiswouldbeanarrayinstead) |
23 | listener,err:=net.Listen( "tcp" , ":1337" ) |
24 | for { |
25 | //acceptaconnection |
26 | connection,_:=listener.Accept() |
27 | gomanageClient(connection) |
28 | } |
29 | } |
首先,我们来看一下main函数。在main函数一开始调用了net.Listen方法,该方法会返回两个值,一个是服务器连接,另一个是错误消息。然后,进入到服务的主循环部分,在这儿程序调用server.Accept方法,然后等待请求。该方法调用后,程序会被挂起,直到有有一个客户端的连接出现。一旦有个连接出现,我们将connection对象传值到manageClient方法中,由于通过goroutine的方式调用manageClient,所以主程序会继续等待处理下一个客户端连接请求。
最后,关于这个manageClient方法要注意一下。首先,注意一下参数表,是变量名在先,类型在后。这样的格式多少是由Go语言创造者决定的。你可能甚至可能一周后都没有注意到。
在方法体中,向客户端写入“Hi!”信息,然后关闭套接字。
好了,就这么几行代码,我们轻松完成了一个基础服务器。你可以将它改成一个HTTP代理(如果加上缓存,那就更棒了)。Goroutines支持我们这么做。事实上goroutine不单单是一个轻量级的线程,因为还有许多与众不同的机制在背后在起着作用,所以才可以通过如此简练的代码的来实现goroutine功能。
Channels
虽然,单纯只有Goroutines已经很有作用了,但是如果在channels概念的支持下,那么Goroutines将更具威力。Channels是一种goroutine之间或者goroutine和主进程之间的通信机制。让我们来看个简单的实例。01 | packagemain |
02 |
03 | import( |
04 | "fmt" |
05 | ) |
06 |
07 | vareventChannelchan int =make(chan int ) |
08 |
09 | funcsayHello(){ |
10 | fmt.Println( "Hello,world!" ) |
11 |
12 | //passamessagethroughtheeventChannel |
13 | //itdoesn'tmatter*what*weactuallysendacross |
14 | eventChannel<-1 |
15 | } |
16 |
17 | funcmain(){ |
18 |
19 | //runagoroutinethatsayshello |
20 | gosayHello() |
21 |
22 | //readtheeventChannel |
23 | //thiscallblockssoitwaitsuntilsayHello() |
24 | //isdone |
25 | <-eventChannel |
26 | } |
这儿有一点很重要:默认情况下,如果channel中没有数据的情况下,从channel中读数据会被阻塞的,一直阻塞到可以从channel中读到数据。
来的稍微复杂的:
01 | packagemain |
02 |
03 | import( |
04 | "fmt" |
05 | ) |
06 |
07 | varlogChannelchanstring=make(chanstring) |
08 |
09 | funcloggingLoop(){ |
10 | for { |
11 | //waitforamessagetoarrive |
12 | msg:=<-logChannel |
13 |
14 | //logthemsg |
15 | fmt.Println(msg) |
16 | } |
17 | } |
18 |
19 | funcmain(){ |
20 | gologgingLoop() |
21 |
22 | //dosomestuffhere |
23 | logChannel<- "messagedtobelogged" |
24 | //dootherstuffhere |
25 | } |
就这样,短短几行代码,我们就完成了一个main函数和goroutines之间的通信。由于共享内存的通信方式,存在着诸如互斥锁,竞态条件等问题,早已成为了开发者的噩梦。但是在Go中,channels的概念解决了多数传统问题。此外,Go的channels是语言的固有部分,而非附加在某个库中的。
与Ruby相比,Go的goroutines实际上是运行在后台,并且由语言本身实现的(MRIRuby整个运行在一个单独的线程中,所以它不能提供一个真实的并行)。此外,虽然Ruby自带线程实现,但是那实在不好使用。事实上,
告一段落(暂时)
这篇文章我们已经讲了不少东西了,首先介绍了一些非常基础的语法,然后直接介绍了Go语言的并发机制。请继续关注后续的第2部分,那里我们会接触一些复杂语法,和其他一些Go语言带给我们的牛叉特性。
本文地址:
原文地址:
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照
相关文章推荐
- 为Java程序员而准备的Go入门教程指南
- 为 Java 程序员准备的 Go 入门 PPT
- 为Java程序员准备的Go语言入门PPT
- Go 语言基础教程:10分钟入门
- PyQt4入门教程(1)_PyQt介绍及准备工作
- Ruby学习入门教程总结
- ruby入门教程:入门ruby的方法
- 【Go入门教程9】并发(goroutine,channels,Buffered Channels,Range和Close,Select,超时,runtime goroutine)
- Angular系列------AngularJS入门教程:导言和准备(转载)
- Ruby学习入门教程总结
- python爬虫入门教程(一):开始爬虫前的准备工作
- 为Java程序员编写的Scala的入门教程
- 【Go入门教程8】interface(interface类型、interface值、空interface{}、嵌入interface、反射)
- 【Go入门教程8】总结(25个关键字)
- Ruby On Rails系列从入门到精通实战教程(Ruby基础+Rails框架+网上商城项目实战)
- Go Web框架gin的入门教程
- Ruby入门教程和技巧
- AngularJS入门教程:导言和准备
- Ruby on Rails下的图像处理入门教程
- [入门]Ruby on Rails入门教程及开发工具选用