goroutine
- 順序執行 :一個任務完成之後再執行下一個
- 並行: 不必等到一個操作執行完畢後再執行下一個
併發和並行
併發就是同時處理很多事情,而並行就是同時做很多事情。
- 並行:指同一時刻同時進行,進程並行需要多個處理器支持。
- 併發:指一段時間內的多個進程輪流切換使CPU。
模擬阻塞執行
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 模擬阻塞的代碼
package main
import (
"fmt"
"time"
)
func main () {
slowFunc()
fmt.Println("I'm not shown until slowFunc() completes.")
}
//
func slowFunc () {
fmt.Println("Sleepper() start")
time.Sleep(time.Second)
fmt.Println("Sleepper() finished")
}
goroutine 使用: 只需要讓Goroutine執行的函數或方法前加上關鍵字go即可
使用 go關鍵字
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 使用goroutine
package main
import (
"fmt"
"time"
)
func main () {
go slowFunc()
fmt.Println("I'm not shown straightaway!")
}
//
func slowFunc () {
fmt.Println("Sleeper() started")
time.Sleep(time.Second)
fmt.Println("Sleeper() finished")
}
程序輸出一行就直接退出了,因爲goroutine 立即返回,程序直接執行後面的代碼,然後退出,如果沒有其他因素阻止,程序將在 goroutine 返回前退出。
阻止程序直接退出
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 使用goroutine
import (
"fmt"
"time"
)
func main () {
go slowFunc()
fmt.Println("I'm not shown straightaway!")
time.Sleep(time.Second * 2)
}
//
func slowFunc () {
fmt.Println("Sleeper() started")
time.Sleep(time.Second)
fmt.Println("Sleeper() finished")
}
Goroutine
-
go 在幕後使用線程來管理併發,但Goroutine 讓程序員無需直接管理線程。
-
創建一個Goroutine 只需要佔用幾KB內存,因此即便創建數千個 Goroutine 也不會耗盡內存。
-
另外創建和銷燬Goroutine 的效率也非常的高。
-
併發地執行代碼意味着程序可能更快的執行完畢,並在數據就緒後就返回,而無需等待程序的其他部分結束。
-
阻塞式的代碼讓程序暫停執行。
-
使用並法編程的情景包括從磁盤文件讀取數據,從網絡讀寫數據以及從數據庫讀寫數據。
通道 chan
- 通道和 Goroutine 一起提供了一個受控的環境,通道讓 Goroutine 能夠互相通信,用於開發併發軟件。
如果說 Goroutine 是一種支持併發編程的方式,那麼通道就是一種與 Goroutine 通信的方式。 通道讓數據能夠進入和離開 Goroutine, 可方便 Goroutine 之間的通信。
- 不要通過共享內存來通信,而通過通信來共享內存
- 通道只能有一種數據類型,可以創建任意類型的通道,因此可以使用結構體來存儲複雜的數據結構
使用通道進行通信
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 使用通道進行通信
import (
"fmt"
"time"
)
func main () {
c := make(chan string)
go slowFunc(c)
msg := <-c
fmt.Println(msg)
}
//
func slowFunc (c chan string) {
time.Sleep(time.Second * 2)
c <- "slowFunc() finished"
}
使用緩衝通道
- 緩衝通道只能存儲指定數量的消息
如果向它發送更多的消息,將導致錯誤
- close 用來關閉通道,禁止再向通道發送消息
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
//
import (
"fmt"
"time"
)
func main () {
messages := make(chan string, 2)
messages <- "hello"
messages <- "world"
close(messages)
fmt.Println("Pushed two messages onto Channel with no receivers")
time.Sleep(time.Second)
reveiver(messages)
}
//
func reveiver (c chan string) {
for msg := range c {
fmt.Println(msg)
}
}
流程控制
- 給通道指定消息接收者是一個阻塞操作,因爲它將阻止函數返回,直到收到一條消息爲止。
- 從通道接收並打印消息的程序需要阻塞,以免終止。
通道接收一條消息就返回
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 通道接收一條消息就返回
import (
"fmt"
"time"
)
func main () {
messages := make(chan string)
go pinger(messages)
msg := <- messages
fmt.Println(msg)
}
//
func pinger (c chan string) {
t := time.NewTicker(1 * time.Second)
for {
c <- "ping"
<- t.C
}
}
創建不斷監聽通道中消息的監聽器
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 不間斷的指定接收操作
import (
"fmt"
"time"
)
func main () {
messages := make(chan string)
go pinger(messages)
for i := 0 ; i < 5 ; i ++ {
msg := <- messages
fmt.Println(msg)
}
}
//
func pinger (c chan string) {
t := time.NewTicker(1 * time.Second)
for {
c <- "ping"
<- t.C
}
}
將通道作爲函數參數
- 可以進一步指定在函數中如何使用傳入的參數,可在傳遞通道時指定爲只讀,只寫或讀寫的。
指定通道訪問權限
func channelReader(messages <-chan string){
mes := messages
fmt.Println(msg)
}
// 通道在函數內只寫的
func channelWriter (messages chan<- string) {
messages <- "messages"
}
// 沒有指定箭頭,表示通道可讀可寫
func channelReaderWriter (messages chan string) {
msg := <- messages
fmt.Println(msg)
messages <- "helloworld"
}
select 語句
結合使用 select 語句和通道
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// select 語句使用
import (
"fmt"
"time"
)
func main () {
channel1 := make(chan string)
channel2 := make(chan string)
go ping1(channel1)
go ping2(channel2)
select {
case msg1 := <- channel1:
fmt.Println("reveived", msg1)
case msg2 := <- channel2:
fmt.Println("reveived", msg2)
}
}
//
func ping1 (c chan string) {
time.Sleep(time.Second)
c <- "ping on channel1"
}
//
func ping2 (c chan string) {
time.Sleep(time.Second * 2)
c <- "ping on channel2"
}
- 哪條消息最先到達,將執行哪條case 語句
- 即根據最先收到的消息採取相應的措施
給 select 語句指明超時時間
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 給select 語句指定超時時間
import (
"fmt"
"time"
)
func main () {
channel1 := make(chan string)
channel2 := make(chan string)
go ping1(channel1)
go ping2(channel2)
select {
case msg1 := <- channel1:
fmt.Println("reveived", msg1)
case msg2 := <- channel2:
fmt.Println("reveived", msg2)
case <- time.After(500 * time.Millisecond):
fmt.Println("no messages receiverd. giving up")
}
}
//
func ping1 (c chan string) {
time.Sleep(time.Second)
c <- "ping on channel1"
}
//
func ping2 (c chan string) {
time.Sleep(time.Second * 2)
c <- "ping on channel2"
}
退出通道
在已知需要停止的時間的情況下,使用超時時間是不錯的選擇,在不確定 select 語句該在何時返回, 因此不能使用定時器。 在這種情況下可以使用退出通道。
- 通過在 select 語句中添加一個退出通道,可向退出通道發送消息來結束該語句,從而停止阻塞。
- 關閉緩衝意味着不能再向它發送消息。緩衝的消息會被保留,可供接收者讀取。
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 使用退出通道
import (
"time"
"fmt"
)
func main () {
messages := make(chan string)
stop := make(chan bool)
go sender(messages)
go func() {
time.Sleep(time.Second * 2)
fmt.Println("time is up!")
stop <- true
}()
for {
select {
case <- stop :
return
case msg := <- messages:
fmt.Println(msg)
}
}
}
//
func sender (c chan string) {
t := time.NewTicker(time.Second)
for {
c <- "I'm sending a messages"
<- t.C
}
}
小練習
接收10 條消息後退出程序
// Filename: goroutine_chan.org[*Org Src goroutine_chan.org[ go ]*]
// coding:utf-8
// 通道接收後退出
import (
"fmt"
"time"
)
func main () {
messages := make(chan string)
go sender(messages)
for i := 0; i < 10; i++ {
msg := <- messages
fmt.Println(msg)
}
}
// send messages
func sender (c chan string) {
t := time.NewTicker(time.Second)
for {
c <- "This is a messages."
<- t.C
}
}