Go语言学习笔记(协程与通道1)

协程

  • 在 Go 中,应用程序并发处理的部分被称作 goroutines(协程) ,它可以进行更有效的并发运算。设想这里的一个程序有两个函数,一个函 数做计算,另一个输出结果,假设两个函数没有相互之间的调用关系。一个线性的程序会先调用其中 的一个函数,然后再调用另一个。如果程序中包含多个goroutine,对两个函数的调用则可能发生在 同一时刻。马上就会看到这样的一个程序。
  • 当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。新的 goroutine会用go语句来创建。在语法上,go语句是一个普通的函数或方法调用前加上关键字go。 go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地完成
f()  //执行f();等待它返回
go f() //创建一个新协程执行f();不需要等待它返回

实例1:协程基本概念理解

代码

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("In main()")
	go longWait()
	go shortWait()
	fmt.Println("About to sleep in main()")
	time.Sleep(10 * 1e9)
	fmt.Println("At the end of main()")
}

func longWait() {
	fmt.Println("Beginning longwait()")
	time.Sleep(5 * 1e9) //休眠5秒
	fmt.Println("End of longWait()")
}

func shortWait() {
	fmt.Println("Beginning shortWait()")
	time.Sleep(2 * 1e9) //休眠2秒
	fmt.Println("End of shortWait()")
}

输出结果

Go语言学习笔记(协程与通道1)_第1张图片

解释

  • main() , longWait() 和 shortWait() 三个函数作为独立的处理单元按顺序启动,然后开始并行运行。每一个 函数都在运行的开始和结束阶段输出了消息。我们让 main() 函数暂停 10 秒从而确定它会在另外两个协程之后结束。如果不这样(如果我们让 main() 函数停止 4 秒), main() 会提前结束, longWait() 则无法完成。如果我们不在 main() 中等待,协程会随着程序的 结束而消亡。

  • 当 main() 函数返回的时候,程序退出:它不会等待任何其他非 main 协程的结束。这就是为什么在服务器程序 中,每一个请求都会启动一个协程来处理, server() 函数必须保持运行状态。通常使用一个无限循环来达到这样的 目的。

  • 另外,协程是独立的处理单元,一旦陆续启动一些协程,无法确定他们是什么时候真正开始执行的。我们的代码逻辑 必须独立于协程调用的顺序。

实例2:并发的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
		}
		go 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)
	}
}
  • 客户端
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)
	}
}

分析

  • 这是一个顺序执行的时钟服务器,它会每隔一秒钟将当前时间写到客户端

  • Listen函数创建了一个net.Listener的对象,这个对象会监听一个网络端口上到来的连接,在这 个例子里我们用的是TCP的localhost:8000端口。listener对象的Accept方法会直接阻塞,直到 一个新的连接被创建,然后会返回一个net.Conn对象来表示这个连接。

  • handleConn函数会处理一个完整的客户端连接。在一个for死循环中,用time.Now()获取当前时 刻,然后写到客户端。由于net.Conn实现了io.Writer接口,我们可以直接向其写入内容。这个死 循环会一直执行,直到写入失败。最可能的原因是客户端主动断开连接。这种情况下handleConn函 数会用defer调用关闭服务器侧的连接,然后返回到主函数,继续等待下一个连接请求。

  • 服务端程序会从连接中读取数据,并将读到的内容写到标准输出中,直到遇到end of file的条件或者 发生错误。

  • 在本机中可以同时开启多个客户端程序,由于Go协程的存在,服务端程序会同时与这些服务端建立连接,客户端程序会同时输出当前时间。

(笔记源自《The Go Programming Language》和《The way to Go》,如有侵权请联系删除)

你可能感兴趣的:(学习,开发语言,golang)