技术开发 频道

为Ruby程序员精心准备的Go语言入门教程

  这么说,Go从语言本身支持并发。也就是,Go语言中有并发基元(primitives)。这样意义何在呢?仅仅因为不是由某个库或者模块来实现并发,这好像不是什么了不起的举措啊。但是,实际上goroutine从根本上与线程不同。goroutine更加轻量化。还记得在服务器中,我们不该为每个客户端创建一个线程吧?但是,使用goroutine,情况就不同了:

package main

import (
  
"fmt"
  
"net"
)

//notice that in the arguments, the name of
//the variable comes first, then comes the
//type of the variable, just like in "var"
//declarations
func manageClient(conn net.Conn) {
  conn.Write([]
byte("Hi!"))
  conn.Close()
  
//do something with the client
}

func main() {
  
//we are creating a server her that listens
  
//on port 1337. Notice that, similar to Ruby,
  
//a method can have two return values (although
  
//in Ruby, this would be an array instead)
  listener, err :
= net.Listen("tcp", ":1337")
  
for {
    
//accept a connection
    connection, _ :
= listener.Accept()
    go manageClient(connection)
  }
}

  这些代码似有那么一小点复杂啊,虽然想法是很简单。好吧,让我们一步一步慢慢来 。

  首先,我们来看一下main函数。在main函数一开始调用了net.Listen方法,该方法会返回两个值,一个是服务器连接,另一个是错误消息。然后,进入到服务的主循环部分,在这儿程序调用server.Accept方法,然后等待请求。该方法调用后,程序会被挂起,直到有有一个客户端的连接出现。一旦有个连接出现,我们将connection对象传值到manageClient方法中,由于通过goroutine的方式调用manageClient,所以主程序会继续等待处理下一个客户端连接请求。

  最后,关于这个manageClient方法要注意一下。首先,注意一下参数表,是变量名在先,类型在后。这样的格式多少是由Go语言创造者决定的。你可能甚至可能一周后都没有注意到。

  在方法体中,向客户端写入“Hi!”信息,然后关闭套接字。

  好了,就这么几行代码,我们轻松完成了一个基础服务器。你可以将它改成一个HTTP代理(如果加上缓存,那就更棒了)。Goroutines支持我们这么做。事实上goroutine不单单是一个轻量级的线程,因为还有许多与众不同的机制在背后在起着作用,所以才可以通过如此简练的代码的来实现goroutine功能。

  Channels

  虽然,单纯只有Goroutines已经很有作用了,但是如果在channels概念的支持下,那么Goroutines将更具威力。Channels是一种goroutine之间或者goroutine和主进程之间的通信机制。让我们来看个简单的实例。

package main

import (
  
"fmt"
)

var eventChannel chan
int = make(chan int)

func sayHello() {
  fmt.Println(
"Hello, world!")

  
//pass a message through the eventChannel
  
//it doesn't matter *what* we actually send across
  eventChannel < - 1
}

func main() {

  
//run a goroutine that says hello
  go sayHello()

  
//read the eventChannel
  
//this call blocks so it waits until sayHello()
  
//is done
  
<- eventChannel
}

  程序中有个调用了sayHellothat方法的goroutine,该方法输出 “Hello, world”消息。但是,注意那个eventChannel的声明。本质上,我们声明了一个整型的channel。我们可以通过这个channel来发送数据,而其他部分可以从这个channel中读取数据。这就使得channel成为了一种通信方式。在 sayHello方法中,eventChannel < - 1将整数1加入到eventChannel中,然后在主函数中,我们可以从 eventChannel将数据读出。

  这儿有一点很重要:默认情况下,如果channel中没有数据的情况下,从channel中读数据会被阻塞的,一直阻塞到可以从channel中读到数据。

  来的稍微复杂的:

package main

import (
"fmt"
)

var logChannel chan
string = make(chan string)

func loggingLoop() {
  
for {
    
//wait for a message to arrive
    msg :
= < - logChannel

    
//log the msg
    fmt.Println(msg)
  }
}

func main() {
  go loggingLoop()

  
//do some stuff here
  logChannel
<- "messaged to be logged"
  
//do other stuff here
}

  这里,我们完成了一个main的事件轮询,它会一直处于监听事件状态,也就是loggingLoop函数。它从loggChanne中接收到一个消息后,就会输到屏幕。这是一个非常普片的设计,特别在事件轮询中获得一些状态。

  就这样,短短几行代码,我们就完成了一个main函数和goroutines之间的通信。由于共享内存的通信方式,存在着诸如互斥锁,竞态条件等问题,早已成为了开发者的噩梦。但是在Go中,channels的概念解决了多数传统问题。此外,Go的channels是语言的固有部分,而非附加在某个库中的。

  与Ruby相比,Go的goroutines实际上是运行在后台,并且由语言本身实现的(MRI Ruby整个运行在一个单独的线程中,所以它不能提供一个真实的并行)。此外,虽然Ruby自带线程实现,但是那实在不好使用。事实上,Agent库尝试将一些goroutines精妙的地方引入Ruby中去。

0
相关文章