簡介
channel 是 Go 語言中的一個核心類型,可以把它看成管道。並發核心單元通過它就可以發送或者接收數據進行通訊,這在一定程度上又進一步降低了編程的難度。
channel 是一個數據類型,主要用來解決 go 程的同步問題以及 go 程之間數據共享(數據傳遞)的問題。
goroutine 運行在相同的地址空間,因此訪問共享內存必須做好同步。goroutine 奉行通過通信來共享內存,而不是共享內存來通信。
引⽤類型 channel 可用於多個 goroutine 通訊。其內部實現了同步,確保並發安全(通過 CSP)。

強調一下:
channel 是一個數據類型,對應一個“管道(通道)”。
定義 channel 變量
和 map 類似,channel 也是一個對應 make 創建的底層數據結構的引用。
既然是引用, 那么我們在傳參的時候就能完成在 A 函數棧幀內修改 B 函數棧幀數據的目的. 說白了就是傳的地址.
當我們復制一個 channel 或用於函數參數傳遞時,我們只是拷貝了一個 channel 引用,因此調用者和被調用者將引用同一個 channel 對象。 和其它的引用類型一樣,channel 的零值也是 nil。
定義一個 channel 時,也需要定義發送到 channel 的值的類型。channel 可以使用內置的 make() 函數來創建:
make(chan Type) // 等價於 make(chan Type, 0)
make(chan Type, capacity)
- chan 是創建 channel 所需使用的關鍵字。
- Type 代表指定 channel 收發數據的類型。
當參數 capacity = 0 時,channel 是無緩沖阻塞讀寫的;當 capacity > 0 時,channel 有緩沖、是非阻塞的,直到寫滿 capacity 個元素才阻塞寫入。
channel 非常像生活中的管道,一邊可以存放東西,另一邊可以取出東西。channel 通過操作符 <- 來接收和發送數據,發送和接收數據語法:
channel <- value // 發送 value 到 channel
<- channel // 接收並將其丟棄
x := <- channel // 從 channel 中接收數據, 並賦值給 x
x, ok := <- channel // 功能同上, 同時檢查通道是否已關閉或者是否為空
默認情況下,channel 接收和發送數據都是阻塞的,除非另一端已經准備好,這樣就使得 goroutine 同步變的更加的簡單,而不需要顯式的 lock。
我們先看一下沒有用 channel 的例子:
package main
import (
"fmt"
"time"
)
// 定義一個打印機
func printer(s string) {
for _, value := range s {
fmt.Printf("%c", value)
time.Sleep(time.Millisecond * 300)
}
}
/* 定義兩個人使用打印機 */
func person1() {
printer("hello")
}
func person2() {
printer("world")
}
func main() {
go person1()
go person2()
time.Sleep(time.Second * 5) // 注意,只寫上面兩行會直接運行完畢,想一想 go 程的特性
}
結果:
hwoelrllod
那么,怎么用 channel 實現來保證順序輸出呢?
因為,person1 與 person2 都需要用一個 channel,所以要在全局定義一個 channel。具體代碼如下:
PS:你要傳的什么類型數據與 channel 中定義的類型沒有必然的聯系。
package main
import (
"fmt"
"time"
)
// 全局定義一個 channel,用來完成數據同步
var ch = make(chan int) // 傳的什么類型數據與 channel 中定義的類型沒有必然的聯系
// 定義一個打印機
func printer(s string) {
for _, value := range s {
fmt.Printf("%c", value)
time.Sleep(time.Millisecond * 300)
}
}
/* 定義兩個人使用打印機 */
func person1() {
printer("hello")
ch <- 777
}
func person2() {
<-ch
printer("world")
}
func main() {
go person1()
go person2()
time.Sleep(time.Second * 3) // 注意,只寫上面兩行會直接運行完畢,想一想 go 程的特性
}
這個時候,當運行 person2 函數時,會阻塞在 <-ch 處,運行 person1 函數時,打印完 “hello”,會在 ch <- 777 處阻塞。
但是這時,ch <- 777 對應這寫端已經准備好了,同時 <-ch 對應讀端也已經准備好了,所以代碼就會繼續執行,接下來就會打印 “world”。
我們再來看一段代碼:
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
defer fmt.Println("子 go 程結束")
fmt.Println("子 go 程正在運行 ...")
c <- 666 /// 把 666 發送到 c
}()
num := <-c // 從 c 中接收數據, 並賦值給 num
fmt.Println("num = ", num)
fmt.Println("main go 程結束")
}
運行結果:
子 go 程正在運行 ...
子 go 程結束
num = 666
main go 程結束
以上我們都是用 channel 用來做數據同步,並沒有用到 channel 中的數據,下面我們看一個用 channel 完成數據傳遞的例子:
package main
import "fmt"
func main() {
ch := make(chan string)
// len(ch): channel 中剩余未讀取的數據個數; cap(ch): channel 的容量
fmt.Println("len(ch) = ", len(ch), "cap(ch) = ", cap(ch))
go func() {
for i := 0; i < 2; i++ {
fmt.Println("i = ", i)
}
ch <- "子 go 程打印完畢"
}()
str := <-ch
fmt.Println(str)
}
注意:len(ch): channel 中剩余未讀取的數據個數; cap(ch): channel 的容量
運行結果:
len(ch) = 0 cap(ch) = 0
i = 0
i = 1
子 go 程打印完畢
強調一下:
channel 有兩個端:
- 寫端(傳入端):chan <- 777
- 讀端(傳出端):<- chan
要求:讀端和寫端必須同時滿足條件(讀端有數據可讀,寫端有數據可寫),才能在 channel 中完成數據流動。否則,阻塞。
【補充知識點】
每當有一個進程啟動時,系統會自動打開三個文件:標准輸入、標准輸出、標准錯誤,對應三個文件:stdin、stdout、stderr。
當進程運行結束時,系統會自動關閉這三個文件。
無緩沖的channel - 同步通信
無緩沖的通道(unbuffered channel)是指在接收前沒有能力保存任何值的通道。
這種類型的通道要求發送 goroutine 和接收 goroutine 同時准備好,才能完成發送和接收操作。否則,通道會導致先執行發送或接收操作的 goroutine 阻塞等待。
這種對通道進行發送和接收的交互行為本身就是同步的。其中任意一個操作都無法離開另一個操作單獨存在。
阻塞:由於某種原因數據沒有到達,當前協程(線程)持續處於等待狀態,直到條件滿足,才接觸阻塞。
同步:在兩個或多個協程(線程)間,保持數據內容一致性的機制。
下圖展示兩個 goroutine 如何利用無緩沖的通道來共享一個值:

簡單說明:
- 在第 1 步,兩個 goroutine 都到達通道,但哪個都沒有開始執行發送或者接收。
- 在第 2 步,左側的 goroutine 將它的手伸進了通道,這模擬了向通道發送數據的行為。這時,這個 goroutine 會在通道中被鎖住,直到交換完成。
- 在第 3 步,右側的 goroutine 將它的手放入通道,這模擬了從通道里接收數據。這個 goroutine 一樣也會在通道中被鎖住,直到交換完成。
- 在第 4 步和第 5 步,進行交換,並最終,在第 6 步,兩個 goroutine 都將它們的手從通道里拿出來,這模擬了被鎖住的 goroutine 得到釋放。兩個 goroutine 現在都可以去做別的事情了。
無緩沖的 channel 創建格式:
make(chan Type) // 等價於 make(chan Type, 0)
如果沒有指定緩沖區容量,那么該通道就是同步的,因此會阻塞到發送者准備好發送和接收者准備好接收。
例如:
package main
import (
"fmt"
"time"
)
func main() {
// 創建無緩沖的 channel
ch := make(chan int, 0)
go func() {
defer fmt.Println("子 go 程結束")
for i := 0; i < 3; i++ {
fmt.Println("子 go 程正在運行, i = ", i)
ch <- i
}
}()
time.Sleep(time.Second) // 延時一秒
for i := 0; i < 3; i++ {
// 從 ch 中接收數據, 並賦值給 num
num := <-ch
fmt.Println("num = ", num)
}
fmt.Println("main go程結束")
}
運行結果:
子 go 程正在運行, i = 0
num = 0
子 go 程正在運行, i = 1
子 go 程正在運行, i = 2
num = 1
num = 2
main go程結束
強調一下:
無緩沖 channel 的容量為0。
channel 至少應用於兩個 go 程中:一個讀、另一個寫。
具備同步能力。讀、寫同步。(比如 打電話)
有緩沖的channel - 異步通信
有緩沖的通道(buffered channel)是一種在被接收前能存儲一個或者多個數據值的通道。
這種類型的通道並不強制要求 goroutine 之間必須同時完成發送和接收。通道會阻塞發送和接收動作的條件也不同。
只有通道中沒有要接收的值時,接收動作才會阻塞。
只有通道沒有可用緩沖區容納被發送的值時,發送動作才會阻塞。
這導致有緩沖的通道和無緩沖的通道之間的一個很大的不同:無緩沖的通道保證進行發送和接收的 goroutine 會在同一時間進行數據交換;有緩沖的通道沒有這種保證。
使用有緩沖channel在goroutine之間同步的示例圖:

- 在第 1 步,右側的 goroutine 正在從通道接收一個值。
- 在第 2 步,右側的這個 goroutine 獨立完成了接收值的動作,而左側的 goroutine 正在發送一個新值到通道里。
- 在第 3 步,左側的 goroutine 還在向通道發送新值,而右側的 goroutine 正在從通道接收另外一個值。這個步驟里的兩個操作既不是同步的,也不會互相阻塞。
- 最后,在第 4 步,所有的發送和接收都完成,而通道里還有幾個值,也有一些空間可以存更多的值。
有緩沖的 channel 創建格式:
make(chan Type, capacity)
如果給定了一個緩沖區容量,通道就是異步的。只要緩沖區有未使用空間用於發送數據,或還包含可以接收的數據,那么其通信就會無阻塞地進行。
請看以下代碼:
package main
import (
"fmt"
"time"
)
func main() {
// 創建一個有緩沖的 channel
ch := make(chan int, 3) // 存滿 3 個元素之前不會阻塞
// 查看一下 channel 的未被讀取的緩沖元素數量以及 channel 容量
fmt.Printf("len(ch) = %d, cap(ch) = %d\n", len(ch), cap(ch))
go func() {
defer fmt.Println("子 go 程結束")
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("子 go 程正在運行, i = ", i)
}
}()
time.Sleep(time.Second)
for i := 0; i < 5; i++ {
num := <-ch
fmt.Println("num = ", num)
}
fmt.Println("main go 程結束")
}
運行結果:
len(ch) = 0, cap(ch) = 3
子 go 程正在運行, i = 0
子 go 程正在運行, i = 1
子 go 程正在運行, i = 2
num = 0
num = 1
num = 2
num = 3
子 go 程正在運行, i = 3
子 go 程正在運行, i = 4
子 go 程結束
num = 4
main go 程結束
強調一下:
有緩沖 channel 的容量大於 0。
channel 應用於兩個 go 程中:一個讀、另一個寫。
緩沖區可以進行數據存儲,存儲至容量上限才阻塞。
具備異步的能力,不需要同時操作 channel 緩沖區。(比如發短信)
關閉channel
如果發送者知道,沒有更多的值需要發送到 channel 的話,那么讓接收者也能及時知道沒有多余的值可接收將是有用的,因為接收者可以停止不必要的接收等待。
這可以通過內置的 close 函數來關閉 channel 實現。當我們確定不再向對端發送、接收數據時,我們可以關閉 channel。(一般關閉發送端)
對端可以判斷 channel 是否關閉:
if num, ok := <- ch; ok {
// 對端沒有關閉,num 保存讀到的數據
} else {
// 對端已經關閉,num 保存對應類型的零值
}
例如:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
// 如果沒有 close(ch), 那么當程序打印完 0 1 2 3 4 時, 會因為沒有寫端 channel 造成死鎖
close(ch) // 寫端,寫完數據主動關閉 channel
}()
// 從 channel 中讀取數據,但是不知道讀多少次,我們可以判斷當 channel 關閉時意味着讀取數據完畢
for true {
// ok 為 true說明 channel 沒有關閉, 為 false 說明 channel 已經關閉
if data, ok := <-ch; ok {
fmt.Println("寫端沒有關閉,data = ", data)
} else {
fmt.Println("寫端關閉,data = ", data)
break
}
}
fmt.Println("結束.")
}
運行結果:
寫端沒有關閉,data = 0
寫端沒有關閉,data = 1
寫端沒有關閉,data = 2
寫端沒有關閉,data = 3
寫端沒有關閉,data = 4
寫端關閉,data = 0
結束.
我們也可以用 for range 獲取 channel 中的數據:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 5)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
// 如果沒有 close(ch), 那么當程序打印完 0 1 2 3 4 時, 會因為沒有寫端 channel 造成死鎖
close(ch) // 寫端,寫完數據主動關閉 channel
fmt.Println("子 go 程結束")
}()
time.Sleep(time.Second)
// 使用 for range 循環讀取 channel 的數據,注意這里前面只接收一個變量
for num := range ch {
fmt.Println(num)
}
fmt.Println("結束.")
}
運行結果:
子 go 程結束
0
1
2
3
4
結束.
強調一下:
- channel 不像文件一樣需要經常去關閉,只有當你確實沒有任何發送數據了,或者你想顯式的結束 range 循環之類的,才去關閉 channel。簡單說就是數據沒發送完,不應該關閉 channel
- 關閉 channel 后,無法向 channel 再發送數據(引發 panic 錯誤后導致接收立即返回零值)【panic: send on closed channel】
- 寫端關閉 channel 后,可以繼續從 channel 接收數據
- 如果 channel 中無數據,則讀到的為對應類型的零值(注意與無緩沖 channel 的區別)
- 如果 channel 中有數據,則先讀該數據,讀完數據后,繼續讀則讀到的為對應類型的零值
- 對於 nil channel,無論收發都會被阻塞。
- 可以使用 for range 替代 ok 那種形式:
for num := range ch{} // 注意形式,不是 <-ch
單向 channel 及應用
默認情況下,通道 channel 是雙向的,也就是,既可以往里面發送數據也可以同里面接收數據。
但是,我們經常見一個通道作為參數進行傳遞而只希望對方是單向使用的,要么只讓它發送數據,要么只讓它接收數據,這時候我們可以指定通道的方向。

單向 channel 變量的聲明非常簡單,如下:
var ch1 chan int // ch1 是一個正常的 channel,是雙向的
var ch2 chan<- float64 // ch2 是一個單向 channel,只能用於寫 float64 數據
var ch3 <-chan int // ch3 是一個單向 channel,只能用於讀 int 數據
chan<-表示數據進入管道,要把數據寫進管道,對於調用者就是輸出。<-chan表示數據從管道出來,對於調用者就是得到管道的數據,當然就是輸入。
可以將 channel 隱式轉換為單向隊列,只收或只發,不能將單向 channel 轉換為雙向 channel:
ch := make(chan int, 3)
var sendCh chan<- int = ch // 只寫
var recvCh <-chan int // 只讀
來看一下單向 channel 的簡單示例(記住了,channel 是傳引用):
package main
import "fmt"
// 只寫
func send(sendCh chan<- int) {
sendCh <- 777
close(sendCh)
}
// 只讀
func recv(recvCh <-chan int) {
num := <-recvCh
fmt.Println("num = ", num)
}
func main() {
ch := make(chan int)
go send(ch)
recv(ch)
}
運行結果:
num = 777
生產者消費模型
生產者消費者模型分析
單向 channel 最典型的應用是: 生產者消費者模型.
所謂生產者消費者模型: 某個模塊(函數等)負責產生數據, 這些數據由另一個模塊來負責處理(此處的模塊是廣義的, 可以是類, 函數, 協程, 線程, 進程等). 產生數據的模塊, 就形象地稱為生產者; 而處理數據的模塊, 就稱為消費者.
單單抽象出生產者和消費者, 還夠不上是生產者消費者模型. 該模式還需要有一個緩沖區處於生產者和消費者之間, 作為一個中介. 生產者把數據放入緩沖區, 而消費者從緩沖區取出數據. 如下圖所示

可以這樣理解, 假設你要寄一封信, 大致過程如下:
- 把信寫好 -- 相當於生產者制造數據
- 把信放入郵筒 -- 相當於生產者把數據放入緩沖區
- 郵遞員把信從郵筒取出 -- 相當於消費者把數據取出緩沖區
- 郵遞員把信拿去郵局做相應的處理 -- 相當於消費者處理數據
那么, 這個緩沖區有什么用呢? 為什么不讓生產者直接調用消費者的某個函數, 直接把數據傳遞過去, 而去設置一個緩沖區呢?
緩沖區的好處大概如下:
1: 解耦 ( 降低 生產者 和 消費者 之間的耦合度 )
假設生產者和消費者分別是兩個類. 如果讓生產者直接調用消費者的某個方法, 那么生產者對於消費者就會產生依賴(也就是耦合). 將來如果消費者的代碼發生變化, 可能會直接影響到生產者. 而如果兩者都依賴某個緩沖區, 兩者之間不直接依賴, 耦合度也就相應降低了.
依然用寄信的例子簡單說一下, 假設生產者就是你, 你負責寫信, 如果沒有郵筒(即緩沖區), 你就需要直接把信給郵遞員(消費者). 但是, 過了幾個月, 郵遞員換人了, 你想要寄信就必須再認識新的郵遞員, 你剛和新的郵遞員熟悉之后, 又換了一個郵遞員, 你又要重新認識... 這就顯得很麻煩, 就是想寄個信而已, 不想認識那么多郵遞員...
但是如果有郵筒(緩沖區)呢, 無論郵遞員怎么更換, 這個與你無關, 我依然是把信放入郵筒就可以了. 這樣一來, 就簡單多了.
2: 提高並發能力 ( 生產者與消費者數量不對等時, 能保持正常通信 )
生產者直接調用消費者的某個方法, 還有另一個弊端
由於函數調用是同步的(或者叫阻塞的), 在消費者的方法沒有返回之前, 生產者只好一直等在那邊. 萬一消費者處理數據很慢, 生產者只能白白浪費時間.
使用了生產者/消費者模式之后, 生產者和消費者可以是兩個獨立的並發主體.
生產者把制造出來的數據放入緩沖區, 就可以再去生產下一個數據. 基本上不用依賴消費者的處理速度.
其實最初這個生產者消費者模式, 主要就是用來處理並發問題的.
從寄信的例子來看, 如果沒有郵筒, 你得拿着信傻站在路口等郵遞員過來收(相當於生產者阻塞); 又或者郵遞員得挨家挨戶問, 誰要寄信(相當於消費者輪詢).
3: 緩存 ( 生產者與消費者數據處理速度不一致時, 暫存數據 )
如果生產者制造數據的速度時快時慢, 緩沖區的好處就體現出來了.
當數據制造快的時候, 消費者來不及處理, 未處理的數據可以暫時存在緩沖區中. 等生產者的制造速度慢下來, 消費者再慢慢處理掉.
再拿寄信的例子舉例, 假設郵遞員一次只能帶走1000封信. 萬一某次碰上情人節送賀卡, 需要寄出的信超過1000封, 這時候郵筒這個緩沖區就派上用場了. 郵遞員把來不及帶走的信暫存在郵筒中, 等下次過來時再拿走.
生產者消費者模型實現
先來看一下無緩沖的例子
package main
import "fmt"
// 生產者
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
fmt.Println("生產者寫入數據, num = ", i)
ch <- i
}
close(ch)
}
// 消費者
func consumer(ch <-chan int) {
for num := range ch {
fmt.Println("消費者拿到數據, num = ", num)
}
}
func main() {
// 無緩沖 channel
ch := make(chan int)
go producer(ch) // 子 go 程,生產者
consumer(ch) // 主 go 程,消費者
}
運行結果:
生產者寫入數據, num = 0
生產者寫入數據, num = 1
消費者拿到數據, num = 0
消費者拿到數據, num = 1
生產者寫入數據, num = 2
生產者寫入數據, num = 3
消費者拿到數據, num = 2
消費者拿到數據, num = 3
生產者寫入數據, num = 4
消費者拿到數據, num = 4
再來看一下有緩沖的例子 兩者對比結果
package main
import "fmt"
// 生產者
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
fmt.Println("生產者寫入數據, num = ", i)
ch <- i
}
close(ch)
}
// 消費者
func consumer(ch <-chan int) {
for num := range ch {
fmt.Println("消費者拿到數據, num = ", num)
}
}
func main() {
// 有緩沖 channel
ch := make(chan int, 2)
go producer(ch) // 子 go 程,生產者
consumer(ch) // 主 go 程,消費者
}
運行結果:
生產者寫入數據, num = 0
生產者寫入數據, num = 1
生產者寫入數據, num = 2
生產者寫入數據, num = 3
消費者拿到數據, num = 0
消費者拿到數據, num = 1
消費者拿到數據, num = 2
消費者拿到數據, num = 3
生產者寫入數據, num = 4
消費者拿到數據, num = 4
簡單說明
首先創建一個雙向的 channel, 然后開啟一個新的 goroutine, 把雙向通道作為參數傳遞到 producer 方法中, 同時轉成只寫通道. 子 go 程開始執行循環, 向只寫通道中添加數據, 這就是生產者.
主 go 程直接調用 consumer 方法, 該方法將雙向通道轉成只讀通道, 通過循環每次從通道中讀取數據, 這就是消費者.
注意, channel 作為參數傳遞, 是引用傳遞.
生產者消費者 - 模擬訂單
在實際的開發中, 生產者消費者模式應用也非常的廣泛.
例如, 在電商網站中, 訂單處理, 就是非常典型的生產者消費者模式.
當很多用戶單擊下訂單按鈕后, 訂單生產的數據全部放到緩沖區(隊列)中, 然后消費者將隊列中的數據取出來發送至倉庫管理等系統.
通過生產者消費者模式, 將訂單系統與倉庫管理系統隔離開, 且用戶可以隨時下單(生產數據). 如果訂單系統直接調用倉庫系統, 那么用戶單擊下訂單按鈕后, 要等到倉庫系統的結果返回, 這樣速度很慢.
接下來我們就來模擬一下訂單處理的過程.
package main
import "fmt"
type OrderInfo struct {
id int
}
func producer2(out chan<- OrderInfo) { // 生成訂單 -- 生產者
for i:=0; i < 10; i++ { // 循環生成10個訂單
order := OrderInfo{id: i+1}
fmt.Println("生成的訂單ID: ", order.id)
out <- order
}
close(out) // 寫完, 關閉channel
}
func consumer2(in <-chan OrderInfo) { // 處理訂單 -- 消費者
for order := range in { // 從channel取出訂單
fmt.Println("訂單ID為: ", order.id) // 模擬處理訂單
}
}
func main() {
ch := make(chan OrderInfo, 5)
go producer2(ch)
consumer2(ch)
}
簡單說明: OrderInfo 為訂單信息, 這里為了簡單只定義了一個訂單編號屬性, 然后生產者模擬生成10個訂單, 消費者對產生的訂單進行處理.
定時器
time.Timer
Timer 是一個定時器. 代表未來的一個單一事件, 你可以告訴 Timer 你要等待多長時間.
type Timer struct {
C <- chan Time
r runtimeTimer
}
它提供一個channel, 在定時時間到達之前, 沒有數據寫入 Timer.C 會一直阻塞. 直到定時時間到, 系統會自動向 Timer.C 這個channel中寫入當前時間, 阻塞即被解除.
定時器的啟動
示例代碼:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("當前時間: ", time.Now())
// 創建定時器, 指定定時時長
myTimer := time.NewTimer(time.Second * 2)
// 定時到達后, 系統會自動向定時器的成員 C 寫入系統當前系統時間
//讀取 myTimer.C 得到定時后的系統時間, 並完成一次chan的讀操作.
nowTime := <- myTimer.C
fmt.Println("當前時間: ", nowTime)
}
3 種定時方法
1. Sleep
time.Sleep(time.Second)
2. Time.C
fmt.Println("當前時間: ", time.Now())
myTimer := time.NewTimer(time.Second * 2)
nowTime := <- myTimer.C
fmt.Println("現在時間: ", nowTime)
3. time.After
fmt.Println("當前時間: ", time.Now())
nowTime := <- time.After(time.Second * 2)
fmt.Println("現在時間: ", nowTime)
定時器的停止
package main
import (
"fmt"
"time"
)
func main(){
myTimer := time.NewTimer(time.Second * 3) // 創建定時器
go func() {
<- myTimer.C
fmt.Println("子go程, 定時完畢")
}()
myTimer.Stop() // 設置定時器停止
for {
;
}
}
死循環只是為了方便查看結果.
定時器的重置
package main
import (
"fmt"
"time"
)
func main() {
myTimer := time.NewTimer(time.Second * 10)
myTimer.Reset(time.Second * 2) // 重置定時時長為 2 秒
go func(){
<- myTimer.C
fmt.Println("子go程, 定時完畢")
}()
for {
;
}
}
- 創建定時器: myTimer := time.NewTimer(time.Second * 2)
- 停止定時器: myTimer.Stop() [此時 <- myTimer.C 會阻塞]
- 重置定時器: myTimer.Reset(time.Second * 2)
周期定時器 Time.Ticker
Ticker是一個周期觸發定時的計時器, 它會按照一個時間間隔往channel發送系統當前時間, 而channel的接受者可以以固定的時間間隔從channel中讀取.
type Ticker struct {
C <- chan Time
r runtimeTimer
}
package main
import (
"fmt"
"time"
)
func main() {
myTicker := time.NewTicker(time.Second) // 定義一個周期定時器
go func() {
for {
nowTime := <- myTicker.C
fmt.Println("現在時間: ", nowTime)
}
}()
// 死循環, 特地不讓main goroutine結束
for {
;
}
}
package main
import (
"fmt"
"time"
)
func main(){
quit := make(chan bool) // 創建一個判斷是否終止的channel
myTicker := time.NewTicker(time.Second) // 定義一個周期定時器
go func() {
i := 0
for {
nowTime := <- myTicker.C
i++
fmt.Println("現在時間: ", nowTime)
if i == 5 {
quit <- true // 解除 主go程阻塞
}
}
}()
<- quit // 在子go程循環獲取 <- myTicker.C 期間, 一直阻塞
}
李培冠博客
歡迎訪問我的個人網站:
李培冠博客:lpgit.com
