有人把Go比作21世紀的C語言,第一是因為Go語言設計簡單,第二,21世紀最重要的就是并行程序設計,而Go從語言層面就支持了并行。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md#goroutine)goroutine
goroutine是Go并行設計的核心。goroutine說到底其實就是線程,但是它比線程更小,十幾個goroutine可能體現在底層就是五六個線程,Go語言內部幫你實現了這些goroutine之間的內存共享。執行goroutine只需極少的棧內存(大概是4~5KB),當然會根據相應的數據伸縮。也正因為如此,可同時運行成千上萬個并發任務。goroutine比thread更易用、更高效、更輕便。
goroutine是通過Go的runtime管理的一個線程管理器。goroutine通過`go`關鍵字實現了,其實就是一個普通的函數。
~~~
go hello(a, b, c)
~~~
通過關鍵字go就啟動了一個goroutine。我們來看一個例子
~~~
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched()
fmt.Println(s)
}
}
func main() {
go say("world") //開一個新的Goroutines執行
say("hello") //當前Goroutines執行
}
// 以上程序執行后將輸出:
// hello
// world
// hello
// world
// hello
// world
// hello
// world
// hello
~~~
我們可以看到go關鍵字很方便的就實現了并發編程。 上面的多個goroutine運行在同一個進程里面,共享內存數據,不過設計上我們要遵循:不要通過共享來通信,而要通過通信來共享。
> runtime.Gosched()表示讓CPU把時間片讓給別人,下次某個時候繼續恢復執行該goroutine。
>
> 默認情況下,調度器僅使用單線程,也就是說只實現了并發。想要發揮多核處理器的并行,需要在我們的程序中顯式調用 runtime.GOMAXPROCS(n) 告訴調度器同時使用多個線程。GOMAXPROCS 設置了同時運行邏輯代碼的系統線程的最大數量,并返回之前的設置。如果n < 1,不會改變當前設置。以后Go的新版本中調度得到改進后,這將被移除。這里有一篇Rob介紹的關于并發和并行的文章:[http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide)
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md#channels)channels
goroutine運行在相同的地址空間,因此訪問共享內存必須做好同步。那么goroutine之間如何進行數據的通信呢,Go提供了一個很好的通信機制channel。channel可以與Unix shell 中的雙向管道做類比:可以通過它發送或者接收值。這些值只能是特定的類型:channel類型。定義一個channel時,也需要定義發送到channel的值的類型。注意,必須使用make 創建channel:
~~~
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
~~~
channel通過操作符`<-`來接收和發送數據
~~~
ch <- v // 發送v到channel ch.
v := <-ch // 從ch中接收數據,并賦值給v
~~~
我們把這些應用到我們的例子中來:
~~~
package main
import "fmt"
func sum(a []int, c chan int) {
total := 0
for _, v := range a {
total += v
}
c <- total // send total to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x + y)
}
~~~
默認情況下,channel接收和發送數據都是阻塞的,除非另一端已經準備好,這樣就使得Goroutines同步變的更加的簡單,而不需要顯式的lock。所謂阻塞,也就是如果讀取(value := <-ch)它將會被阻塞,直到有數據接收。其次,任何發送(ch<-5)將會被阻塞,直到數據被讀出。無緩沖channel是在多個goroutine之間同步很棒的工具。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md#buffered-channels)Buffered Channels
上面我們介紹了默認的非緩存類型的channel,不過Go也允許指定channel的緩沖大小,很簡單,就是channel可以存儲多少元素。ch:= make(chan bool, 4),創建了可以存儲4個元素的bool 型channel。在這個channel 中,前4個元素可以無阻塞的寫入。當寫入第5個元素時,代碼將會阻塞,直到其他goroutine從channel 中讀取一些元素,騰出空間。
~~~
ch := make(chan type, value)
value == 0 ! 無緩沖(阻塞)
value > 0 ! 緩沖(非阻塞,直到value 個元素)
~~~
我們看一下下面這個例子,你可以在自己本機測試一下,修改相應的value值
~~~
package main
import "fmt"
func main() {
c := make(chan int, 2)//修改2為1就報錯,修改2為3可以正常運行
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
//修改為1報如下的錯誤:
//fatal error: all goroutines are asleep - deadlock!
~~~
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md#range和close)Range和Close
上面這個例子中,我們需要讀取兩次c,這樣不是很方便,Go考慮到了這一點,所以也可以通過range,像操作slice或者map一樣操作緩存類型的channel,請看下面的例子
~~~
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
~~~
`for i := range c`能夠不斷的讀取channel里面的數據,直到該channel被顯式的關閉。上面代碼我們看到可以顯式的關閉channel,生產者通過內置函數`close`關閉channel。關閉channel之后就無法再發送任何數據了,在消費方可以通過語法`v, ok := <-ch`測試channel是否被關閉。如果ok返回false,那么說明channel已經沒有任何數據并且已經被關閉。
> 記住應該在生產者的地方關閉channel,而不是消費的地方去關閉它,這樣容易引起panic
>
> 另外記住一點的就是channel不像文件之類的,不需要經常去關閉,只有當你確實沒有任何發送數據了,或者你想顯式的結束range循環之類的
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md#select)Select
我們上面介紹的都是只有一個channel的情況,那么如果存在多個channel的時候,我們該如何操作呢,Go里面提供了一個關鍵字`select`,通過`select`可以監聽channel上的數據流動。
`select`默認是阻塞的,只有當監聽的channel中有發送或接收可以進行時才會運行,當多個channel都準備好的時候,select是隨機的選擇一個執行的。
~~~
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x + y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
~~~
在`select`里面還有default語法,`select`其實就是類似switch的功能,default就是當監聽的channel都沒有準備好的時候,默認執行的(select不再阻塞等待channel)。
~~~
select {
case i := <-c:
// use i
default:
// 當c阻塞的時候執行這里
}
~~~
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md#超時)超時
有時候會出現goroutine阻塞的情況,那么我們如何避免整個程序進入阻塞的情況呢?我們可以利用select來設置超時,通過如下的方式實現:
~~~
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case v := <- c:
println(v)
case <- time.After(5 * time.Second):
println("timeout")
o <- true
break
}
}
}()
<- o
}
~~~
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md#runtime-goroutine)runtime goroutine
runtime包中有幾個處理goroutine的函數:
* Goexit
退出當前執行的goroutine,但是defer函數還會繼續調用
* Gosched
讓出當前goroutine的執行權限,調度器安排其他等待的任務運行,并在下次某個時候從該位置恢復執行。
* NumCPU
返回 CPU 核數量
* NumGoroutine
返回正在執行和排隊的任務總數
* GOMAXPROCS
用來設置可以并行計算的CPU核數的最大值,并返回之前的值。
- 第一章 Go環境配置
- 1.1 Go安裝
- 1.2 GOPATH 與工作空間
- 1.3 Go 命令
- 1.4 Go開發工具
- 1.5 小結
- 第二章 Go語言基礎
- 2.1 你好,Go
- 2.2 Go基礎
- 2.3 流程和函數
- 2.4 struct類型
- 2.5 面向對象
- 2.6 interface
- 2.7 并發
- 2.8 總結
- 第三章 Web基礎
- 3.1 Web工作方式
- 3.2 Go搭建一個Web服務器
- 3.3 Go如何使得Web工作
- 3.4 Go的http包詳解
- 3.5 小結
- 第四章 表單
- 4.1 處理表單的輸入
- 4.2 驗證表單的輸入
- 4.3 預防跨站腳本
- 4.4 防止多次遞交表單
- 4.5 處理文件上傳
- 4.6 小結
- 第五章 訪問數據庫
- 5.1 database/sql接口
- 5.2 使用MySQL數據庫
- 5.3 使用SQLite數據庫
- 5.4 使用PostgreSQL數據庫
- 5.5 使用beedb庫進行ORM開發
- 5.6 NOSQL數據庫操作
- 5.7 小結
- 第六章 session和數據存儲
- 6.1 session和cookie
- 6.2 Go如何使用session
- 6.3 session存儲
- 6.4 預防session劫持
- 6.5 小結
- 第七章 文本處理
- 7.1 XML處理
- 7.2 JSON處理
- 7.3 正則處理
- 7.4 模板處理
- 7.5 文件操作
- 7.6 字符串處理
- 7.7 小結
- 第八章 Web服務
- 8.1 Socket編程
- 8.2 WebSocket
- 8.3 REST
- 8.4 RPC
- 8.5 小結
- 第九章 安全與加密
- 9.1 預防CSRF攻擊
- 9.2 確保輸入過濾
- 9.3 避免XSS攻擊
- 9.4 避免SQL注入
- 9.5 存儲密碼
- 9.6 加密和解密數據
- 9.7 小結
- 第十章 國際化和本地化
- 10.1 設置默認地區
- 10.2 本地化資源
- 10.3 國際化站點
- 10.4 小結
- 第十一章 錯誤處理,調試和測試
- 11.1 錯誤處理
- 11.2 使用GDB調試
- 11.3 Go怎么寫測試用例
- 11.4 小結
- 第十二章 部署與維護
- 12.1 應用日志
- 12.2 網站錯誤處理
- 12.3 應用部署
- 12.4 備份和恢復
- 12.5 小結
- 第十三章 如何設計一個Web框架
- 13.1 項目規劃
- 13.2 自定義路由器設計
- 13.3 controller設計
- 13.4 日志和配置設計
- 13.5 實現博客的增刪改
- 13.6 小結
- 第十四章 擴展Web框架
- 14.1 靜態文件支持
- 14.2 Session支持
- 14.3 表單及驗證支持
- 14.4 用戶認證
- 14.5 多語言支持
- 14.6 pprof支持
- 14.7 小結
- 附錄A 參考資料