## 4.14\. Multiplexing
基于管道,我們可以很容易實現一個支持多路客戶端的服務器程序。采用的技巧是將每個客戶端私有的通信管道 作為消息的一部分發送給服務器,然后服務器通過這些管道和客戶端獨立通信。現實中的服務器實現都很復雜, 我們這里只給出一個服務器的簡單實現來展現前面描述的技巧。首先定義一個"request"類型,里面包含一個 客戶端的通信管道。
```
09 type request struct {
10 a, b int
11 replyc chan int
12 }
```
服務器對客戶端發送過來的兩個整數進行運算。下面是具體的函數,函數在運算完之后將結構通過結構中的 管道返回給客戶端。
```
14 type binOp func(a, b int) int
16 func run(op binOp, req *request) {
17 reply := op(req.a, req.b)
18 req.replyc <- reply
19 }
```
第14行現定義一個"binOp"函數類型,用于對兩個整數進行運算。
服務器routine線程是一個無限循環,它接受客戶端請求。然后為每個客戶端啟動一個獨立的routine線程, 用于處理客戶數據(不會被某個客戶端阻塞)。
```
21 func server(op binOp, service chan *request) {
22 for {
23 req := <-service
24 go run(op, req) // don't wait for it
25 }
26 }
```
啟動服務器的方法也是一個類似的routine線程,然后返回服務器的請求管道。
```
28 func startServer(op binOp) chan *request {
29 req := make(chan *request)
30 go server(op, req)
31 return req
32 }
```
這里是一個簡單的測試。首先啟動服務器,處理函數為計算兩個整數的和。接著向服務器發送"N"個請求(無阻塞)。 當所有請求都發送完了之后,再進行驗證返回結果。
```
34 func main() {
35 adder := startServer(func(a, b int) int { return a + b })
36 const N = 100
37 var reqs [N]request
38 for i := 0; i < N; i++ {
39 req := &reqs[i]
40 req.a = i
41 req.b = i + N
42 req.replyc = make(chan int)
43 adder <- req
44 }
45 for i := N-1; i >= 0; i-- { // doesn't matter what order
46 if <-reqs[i].replyc != N + 2*i {
47 fmt.Println("fail at", i)
48 }
49 }
50 fmt.Println("done")
51 }
```
前面的服務器程序有個小問題:當main函數退出之后,服務器沒有關閉,而且可能有一些客戶端被阻塞在 管道通信中。為了處理這個問題,我們可給服務器增加一個控制管道,用于退出服務器。
```
32 func startServer(op binOp) (service chan *request, quit chan bool) {
33 service = make(chan *request)
34 quit = make(chan bool)
35 go server(op, service, quit)
36 return service, quit
37 }
```
首先給"server"函數增加一個控制管道參數,然后這樣使用:
```
21 func server(op binOp, service chan *request, quit chan bool) {
22 for {
23 select {
24 case req := <-service:
25 go run(op, req) // don't wait for it
26 case <-quit:
27 return
28 }
29 }
30 }
```
在服務器函數中,"select"操作服用于從多個通訊管道中選擇一個就緒的管道。如果所有的管道都沒有數據, 那么將等待知道有任意一個管道有數據。如果有多個管道就緒,則隨即選擇一個。服務器處理客戶端請求,如果 有退出消息則退出。
最后是在main函數中保存"quit"管道,然后在退出的時候向服務線程發送停止命令。
```
40 adder, quit := startServer(func(a, b int) int { return a + b })
...
55 quit <- true
```
當然,Go語言及并行編程要討論的問題很多。這個入門只是給出一些簡單的例子。
- 1. 關于本文
- 2. Go語言簡介
- 3. 安裝go環境
- 3.1. 簡介
- 3.2. 安裝C語言工具
- 3.3. 安裝Mercurial
- 3.4. 獲取代碼
- 3.5. 安裝Go
- 3.6. 編寫程序
- 3.7. 進一步學習
- 3.8. 更新go到新版本
- 3.9. 社區資源
- 3.10. 環境變量
- 4. Go語言入門
- 4.1. 簡介
- 4.2. Hello,世界
- 4.3. 分號(Semicolons)
- 4.4. 編譯
- 4.5. Echo
- 4.6. 類型簡介
- 4.7. 申請內存
- 4.8. 常量
- 4.9. I/O包
- 4.10. Rotting cats
- 4.11. Sorting
- 4.12. 打印輸出
- 4.13. 生成素數
- 4.14. Multiplexing
- 5. Effective Go
- 5.1. 簡介
- 5.2. 格式化
- 5.3. 注釋
- 5.4. 命名
- 5.5. 分號
- 5.6. 控制流
- 5.7. 函數
- 5.8. 數據
- 5.9. 初始化
- 5.10. 方法
- 5.11. 接口和其他類型
- 5.12. 內置
- 5.13. 并發
- 5.14. 錯誤處理
- 5.15. Web服務器
- 6. 如何編寫Go程序
- 6.1. 簡介
- 6.2. 社區資源
- 6.3. 新建一個包
- 6.4. 測試
- 6.5. 一個帶測試的演示包
- 7. Codelab: 編寫Web程序
- 7.1. 簡介
- 7.2. 開始
- 7.3. 數據結構
- 7.4. 使用http包
- 7.5. 基于http提供wiki頁面
- 7.6. 編輯頁面
- 7.7. template包
- 7.8. 處理不存在的頁面
- 7.9. 儲存頁面
- 7.10. 錯誤處理
- 7.11. 模板緩存
- 7.12. 驗證
- 7.13. 函數文本和閉包
- 7.14. 試試!
- 7.15. 其他任務
- 8. 針對C++程序員指南
- 8.1. 概念差異
- 8.2. 語法
- 8.3. 常量
- 8.4. Slices(切片)
- 8.5. 構造值對象
- 8.6. Interfaces(接口)
- 8.7. Goroutines
- 8.8. Channels(管道)
- 9. 內存模型
- 9.1. 簡介
- 9.2. Happens Before
- 9.3. 同步(Synchronization)
- 9.4. 錯誤的同步方式
- 10. 附錄
- 10.1. 命令行工具
- 10.2. 視頻和講座
- 10.3. Release History
- 10.4. Go Roadmap
- 10.5. 相關資源