每一个并非的执行单元叫作一个goroutine.设想这里的一个程序有两个函数,一个函数做计算,另一个输出结果,假设两个函数没有相互之间的调用关系。一个线性的程序会先调用其中的一个函数,然后再调用另一个。如果程序中包含多个goroutine,对两个函数的调用规则可能发生再同一时刻。马上就会看到这样的一个程序。
当一个程序启动时,其主函数即再一个单独的goroutine中运行,我们叫它main goroutine。新的goroutine会用go语句来创建。在语法上,go语句是一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地完成。
f() // call f(); wait for it to return
go f() // create a new goroutine that calls f();dont wait
// 示例
main goroutine将计算斐波那契数列的第45个元素值、由于计算函数使用低效的递归,所以会运行相当长时间,在此期间我们想让用户看到一个可见的标识来表明程序依然在正常运行,所以来做一个动画的小图标:
func main(){
go spinner(100 * time.Millisecond) // 开启协程
const n = 45
fibN := fib(n) // slow
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration){
for {
for _, r := range `-\|/`{
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
func fib(x int) int{
if x<2{
return x
}
return fib(x-1) + fib(x-2)
}
动画显示了几秒之后,fib(45)的调用成功的返回,并且打印结果:
Fibonacci(45) = 1134903170
然后主函数返回。主函数返回时,所有的goroutine都会被直接打断,程序退出。除了从主函数退出或者直接终止程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执行,但是之后可以看到一种方式来实现这个目的,通过goroutine之间的通信来让一个goroutine请求其它的goroutine,并让被请求的goroutine自行结束执行。
// 示例:并非的Clock服务
package main
import (
"io"
"log"
"net"
"time"
)
func main(){
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil{
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil{
log.Print(err)
continue
}
handleConn(conn)
}
}
func handleConn(c net.Conn){
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
if err != nil{
return
}
time.Sleep(1 * time.Second)
}
}
Listen函数创建了一个net.Listener的对象,这个对象会监听一个网络端口上到来的连接,在这个例子里我们用的是TCP的localhost:8000端口。listener对象的Accept方法会直接阻塞,直到一个新的连接被创建,然后会返回一个net.Conn对象来表示这个连接。
handleConn函数会处理一个完整的客户端连接。在一个for死循环中,用time.Now()获取当前时刻,然后写到客户端。由于net.Conn实现了io.Writer接口,我们可以直接向其写入内容,这个死循环会一直执行,直到写入失败,最可能的原因是客户端主动断开连接。这种情况下handleConn函数会用defer调用关闭服务器的连接,然后返回到主函数,继续等待下一个连接请求。
time.Time.Format方法提供了一种格式化日期和时间信息的方式。它的参数是一个格式化模板,标识如何来格式化时间,而这个格式化模板限定为Mon Jan 2 03:04:05PM 2006 UTC-0700。有8个部分(周几 月份 一个月的第几天..)
可以以任意的形式来组合前面这个模板;出现在模板中的部分会作为参考来对时间格式进行输出。在上面的例子中我们只用到了小时,分钟和秒。
//客户端连接服务端
package main
import (
"io"
"log"
"net"
"os"
)
func main(){
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil{
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn)
}
func mustCopy(dst io.Writer, src io.Reader){
if _, err := io.Copy(dst, src); err!=nil{
log.Fatal(err)
}
}
上面的服务器程序同一时间只能处理一个客户端连接,这边我们将其改成支持并发。在handleConn函数调用的地方增加go关键字,让每一次handleConn的调用都进入一个独立的goroutine
for{
conn, err := listener.Accept()
if err != nil{
log.Print(err)
contine
}
go handleConn(conn)
}