第8章 Goroutines 和 Channels
Go语言中的并发程序可以用两种手段来实现:goroutine 和 channel,其支持顺序通信进程,或被简称为CSP,CSP是一种并发编程模型,在这种并发编程模型中,值会在不同运行实例中传递,第二个手段便是多线程共享内存
8.3 示例:并发的Echo服务
上一节中clock1服务器中,每一个连接都会创建一个goroutine。现在我们创建一个echo服务器,它的每个连接会有多个Goroutine。大多数echo会返回它们读取到的内容,就下下面这个函数一样
func handleConn(c net.Conn) {
io.Copy(c,c)
c.Close()
}
现在我们让echo服务器模拟一个真实的”回响“”也就是随着时间推移,返回值由大写变为小写再到消失
func echo(c net.Conn,shout string,delay time.Duration) {
fmt.Fprintln(c,"\t",strings.ToUpper(shout))
time.Sleep(delay)
fmt.Fprintln(c,"\t",shout)
time.Sleep(delay)
fmt.Fprintln(c,"%t",strings.ToLower(shout))
}
func handleConn(c net.Conn) {
input := bufio.NewScanner(c)
for input.Scan() {
echo(c,input.Text(), 1*time.Second)
}
c.Close()
}
我们还得升级一下客户端程序
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
go mustCopy(os.Stdout, conn)
mustCopy(conn, os.Stdin)
}
现在我们运行一下,当main goroutine 从标准输入获取内容并将其发送给服务器时,另一个goroutine 会读取并答应服务端的响应,当main goroutine结束时,其它goroutine也会结束
下面这个会话中,客户端的输入是左对齐的,服务端的响应会用缩进来表示,客户端会向服务端喊三次话
./netcat2
Hello?
HELLO?
Hello?
hello?
Is there anybody there?
IS THERE ANYBODY THERE?
Yooo-hooo!
Is there anybody there?
is there anybody there?
YOOO-HOOO!
Yooo-hooo!
yooo-hooo!
^D
$ killall reverb1
但是注意,上述的客户端喊话和服务端回话稍显混乱(刚开始客户端喊了好几次,服务都未回复,后来客户端不再喊话,服务端却回复了好多次,感觉两个不在一个频道)那是因为回响不够,我们使用go关键字怎加goroutines
func handleConn(c net.Conn) {
input := bufio.NewScanner(c)
for input.Scan() {
go echo(c,input.Text(), 1*time.Second)
}
c.Close()
}
go 后跟的函数的参数会在go语句自身执行时被求值,因此input.Text()会在main goroutine中被求值,现在回想是并发并且会按时间来覆盖掉其它响应了
./netcat2
Is there anybody there?
IS THERE ANYBODY THERE?
Yooo-hooo!
Is there anybody there?
YOOO-HOOO!
is there anybody there?
Yooo-hooo!
yooo-hooo!
^D
$ killall reverb2
不仅在处理多个客户的请求时可以使用并发,在处理单个用户请求时也可以,就像上面的用法,但是可能存在安全隐患,所以需要慎重考虑,关于安全性我们接下来会讨论