golang并发示例(二)

1、多路客户端的服务器程序

基于管道,我们可以很容易实现一个支持多路客户端的服务器程序。采用的技巧是将每个客户端私有的通信管道 作为消息的一部分发送给服务器,然后服务器通过这些管道和客户端独立通信。现实中的服务器实现都很复杂, 我们这里只给出一个服务器的简单实现来展现前面描述的技巧。首先定义一个”request”类型,里面包含一个客户端的通信管道。

type request struct { //创建一个request对象

a, b    int
    replyc  chan int

}
服务器对客户端发送过来的两个整数进行运算。下面是具体的函数,函数在运算完之后将结构通过结构中的管道返回给客户端。

type binOp func(a, b int) int //定义一个binOp类型 表示函数 xxx 类型
func run(op binOp, req *request) { //将函数op 和结构体 req传入run函数
reply := op(req.a, req.b)
req.replyc <- reply
}
第14行现定义一个”binOp”函数类型,用于对两个整数进行运算。

服务器routine线程是一个无限循环,它接受客户端请求。然后为每个客户端启动一个独立的routine线程, 用于处理客户数据(不会被某个客户端阻塞)。

func server(op binOp, service chan request) {
for {
req := <-service
go run(op, req) // don’t wait for it
}
}
启动服务器的方法也是一个类似的routine线程,然后返回服务器的请求管道。
func startServer(op binOp) chan
request {
req := make(chan request)
go server(op, req)
return req
}
这里是一个简单的测试。首先启动服务器,处理函数为计算两个整数的和。接着向服务器发送”N”个请求(无阻塞)。 当所有请求都发送完了之后,再进行验证返回结果。
func main() {
adder := startServer(func(a, b int) int { return a + b })
const N = 100
var reqs [N]request
for i := 0; i < N; i++ {
req := &reqs[i]
req.a = i
req.b = i + N
req.replyc = make(chan int)
adder <- req="" }="" for="" i="" :="N-1;">= 0; i– { // doesn’t matter what order
if <-reqs[i].replyc != N + 2
i {
fmt.Println(“fail at”, i)
}
}
fmt.Println(“done”)
}

1、多路客户端的服务器程序(改进版)
前面的服务器程序有个小问题:当main函数退出之后,服务器没有关闭,而且可能有一些客户端被阻塞在 管道通信中。为了处理这个问题,我们可给服务器增加一个控制管道,用于退出服务器。
func startServer(op binOp) (service chan request, quit chan bool) {
service = make(chan
request)
quit = make(chan bool)
go server(op, service, quit)
return service, quit
}
首先给”server”函数增加一个控制管道参数,然后这样使用:
func server(op binOp, service chan *request, quit chan bool) {
for {
select {
case req := <-service:
go run(op, req) // don’t wait for it
case <-quit:
return
}
}
}
在服务器函数中,”select”操作服用于从多个通讯管道中选择一个就绪的管道。如果所有的管道都没有数据, 那么将等待知道有任意一个管道有数据。如果有多个管道就绪,则随即选择一个。服务器处理客户端请求,如果 有退出消息则退出。

最后是在main函数中保存”quit”管道,然后在退出的时候向服务线程发送停止命令。
adder, quit := startServer(func(a, b int) int { return a + b })

quit <- true
当然,Go语言及并行编程要讨论的问题很多。这个入门只是给出一些简单的例子。