第8章 Goroutines 和 Channels
Go语言中的并发程序可以用两种手段来实现:goroutine 和 channel,其支持顺序通信进程,或被简称为CSP,CSP是一种并发编程模型,在这种并发编程模型中,值会在不同运行实例中传递,第二个手段便是多线程共享内存
8.10 示例:聊天服务
现在我们要用一个案例来终结本章(虽然我们前面也讲的是案例)。现在我们构建一个程序,用户可以通过这个服务器向其他所有用户广播文本消息
我们来构建这个程序
package main
import (
"bufio"
"fmt"
"log"
"net"
)
func main() {
//监听服务器请求
listener, err := net.Listen("tcp", "Localhost:8000")
if err != nil {
log.Fatal(err)
}
go broadcaster()
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn)
}
}
type client chan<- string
var (
entering = make(chan client)
leaving = make(chan client)
messages = make(chan string)
)
//监视用户的进入和离开
func broadcaster() {
clients := make(map[client]bool)
for {
select {
case msg := <-messages:
for cli := range clients {
cli <- msg
}
case cli := <-entering:
clients[cli] = true
case cli := <-leaving:
delete(clients, cli)
close(cli)
}
}
}
//处理用户信息
func handleConn(conn net.Conn) {
ch := make(chan string)
go clientWriter(conn, ch)
who := conn.RemoteAddr().String()
ch <- "You are " + who
messages <- who + "has arrived"
entering <- ch
input := bufio.NewScanner(conn)
for input.Scan() {
messages <- who + ":" + input.Text()
}
leaving <- ch
messages <- who + "has left"
conn.Close()
}
func clientWriter(conn net.Conn, ch <-chan string) {
for msg := range ch {
fmt.Fprintln(conn, msg)
}
}
$ go build gopl.io/ch8/chat
$ go build gopl.io/ch8/netcat3
$ ./chat &
$ ./netcat3
You are 127.0.0.1:64208 $ ./netcat3
127.0.0.1:64211 has arrived You are 127.0.0.1:64211
Hi!
127.0.0.1:64208: Hi! 127.0.0.1:64208: Hi!
Hi yourself.
127.0.0.1:64211: Hi yourself. 127.0.0.1:64211: Hi yourself.
^C
127.0.0.1:64208 has left
$ ./netcat3
You are 127.0.0.1:64216 127.0.0.1:64216 has arrived
Welcome.
127.0.0.1:64211: Welcome. 127.0.0.1:64211: Welcome.
^C
127.0.0.1:64211 has left”
使用小程序来聊天
当有n个客户端保持聊天session时,这个程序会有2n+2个并发的goroutine,但是这个程序并不要显式的锁。clients被限制在了一个独立的goroutine中,broadcaster,所以它不能被并发的访问
多个goroutine共享的变量只有这些channel和net.Conn的实例,两个东西都是并发安全的,关于约束,我们在下一章会详细的介绍