這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
go語言中的main函數也是運行在一個單獨的goroutine中的,一般稱為 main goroutine,main函數結束時,會打斷其它 goroutine 的執行,但是其它 goroutine 不會打斷其它的 goroutine 的執行,除非是通過通訊讓對方自行中止。
先來看一個最簡單的並發例子,一個時間伺服器:(偏題了,不應該使用這個例子,應該突出重點,這個例子可以放到tcp那節)
func main() { listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { log.Print(err) continue } go handleConn(conn) } }func handleConn(c net.Conn) { defer c.Close() for { _, err := io.WriteString(c, time.Now().Format("2006/01/02 15:04:05\n")) if err != nil { return } time.Sleep(1 * time.Second) } }
從這個例子可以看出,使用go開啟一個tcp服務非常簡潔,此外從該例子中,我們可以順便瞭解一下go中格式化日期和時間的方式是非常奇葩的,格式化模板限定為Mon Jan 2 03:04:05PM 2006 UTC-0700,可以這樣記:1月2日下午3點4分5秒(200)6年UTC-0700。
可以用 nc 或 telnet 來串連這個tcp服務進行測試, 也可以使用go實現一個簡單的用戶端:
func main() { conn, err := net.Dial("tcp", "localhost:8000") if err != nil { log.Fatal(err) } defer conn.Close() mustCopy(os.Stdout, conn)}func mustCopy(dst io.Writer, src io.Reader) { if _, err := io.Copy(dst, src); err != nil { log.Fatal(err) }}
goroutine是通過 channel 來通訊的, 包括髮送和接收兩種操作。可以通過 make 來聲明一個channel 如 ch = make(chan int),和 map 類似,channel 也只是對應底層資料結構的引用,所以發送方和接收方都將引用同一份對象,channel是參考型別,所以它的零值是 nil。channel 使用的操作符是 <- ,發送操作是指將資料發送到 channel,接收是指從 channel中接收。channel類似於一般的io系統,可以通過 close(ch) 來關閉一個 channel,關閉後,將不能進行發送操作,但可以接收之前未接收完的資料,如果沒有資料可接收,則接收到一個nil。
通過 make(chan int) 這種方式建立的 channel 稱之為無緩衝channel,表示 channel的容量是0,如果要聲明一個有緩衝的channel,可以使用 make(chan int, 100) ,第二個參數表示初始化時的channel容量大小。
一個基於無緩衝Channels的發送操作將導致寄件者goroutine阻塞,直到另一個goroutine在相同的Channels上執行接收操作,當發送的值通過Channels成功傳輸之後,兩個goroutine可以繼續執行後面的語句。反之,如果接收操作先發生,那麼接收者goroutine也將阻塞,直到有另一個goroutine在相同的Channels上執行發送操作。
簡單的說,就是使用無緩衝channel時,一旦通訊發起,必須要等到通訊完成才可以繼續執行,否則將阻塞等待。這也就意味著,無緩衝channel在通訊時,會強制進行一次同步操作,所以無緩衝channel也稱之為同步channel