系統文件介紹
在程序啟動運行時,自動打開,運行結束,自動關閉。
- 鍵盤(硬件)—— 標準輸入(文件)stdin —— 0
- 屏幕(硬件)—— 標準輸出(文件)stdout —— 1
- 屏幕(硬件)—— 標準錯誤(文件)stderr —— 2
channel
1. channel簡介
channel是Go語言中的一個核心類型,可以把它看成管道。并發核心單元通過它就可以發送或者接收數據進行通訊,這在一定程度上又進一步降低了編程的難度。
channel是一個數據類型,主要用來解決協程的同步問題以及協程之間數據共享(數據傳遞)的問題。
goroutine運行在相同的地址空間,因此訪問共享內存必須做好同步。goroutine 奉行通過通信來共享內存,而不是共享內存來通信。
引?類型 channel可用于多個 goroutine 通訊。其內部實現了同步,確保并發安全。
2. 定義channel變量
和map類似,channel也一個對應make創建的底層數據結構的引用。
當我們復制一個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。
示例代碼:
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
defer fmt.Println("子協程結束")
fmt.Println("子協程正在運行……")
c <- 666 //666發送到c
}()
num := <-c //從c中接收數據,并賦值給num
fmt.Println("num = ", num)
fmt.Println("main協程結束")
}
3. 無緩沖的channel
無緩沖的通道(unbuffered channel)是指在接收前沒有能力保存任何值的通道
這種類型的通道要求發送goroutine和接收goroutine同時準備好,才能完成發送和接收操作。否則,通道會導致先執行發送或接收操作的 goroutine 阻塞等待。
這種對通道進行發送和接收的交互行為本身就是同步的。其中任意一個操作都無法離開另一個操作單獨存在。
阻塞:由于某種原因數據沒有到達,當前協程(線程)持續處于等待狀態,直到條件滿足,才接觸阻塞。
同步:在兩個或多個協程(線程)間,保持數據內容一致性的機制。
無緩沖的channel創建格式:
make(chan Type) //等價于make(chan Type, 0)
示例代碼:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 0) //創建無緩沖的通道 c
//內置函數 len 返回未被讀取的緩沖元素數量,cap 返回緩沖區大小
fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))
go func() {
defer fmt.Println("子協程結束")
for i := 0; i < 3; i++ {
c <- i
fmt.Printf("子協程正在運行[%d]: len(c)=%d, cap(c)=%d\n", i, len(c), cap(c))
}
}()
time.Sleep(2 * time.Second) //延時2s
for i := 0; i < 3; i++ {
num := <-c //從c中接收數據,并賦值給num
fmt.Println("num = ", num)
}
fmt.Println("main協程結束")
}
4. 有緩沖的channel
有緩沖的通道(buffered channel)是一種在被接收前能存儲一個或者多個數據值的通道。
這種類型的通道并不強制要求 goroutine 之間必須同時完成發送和接收。通道會阻塞發送和接收動作的條件也不同。
只有通道中沒有要接收的值時,接收動作才會阻塞。
只有通道沒有可用緩沖區容納被發送的值時,發送動作才會阻塞。
這導致有緩沖的通道和無緩沖的通道之間的一個很大的不同:無緩沖的通道保證進行發送和接收的 goroutine
會在同一時間進行數據交換;有緩沖的通道沒有這種保證。
有緩沖的channel創建格式:
make(chan Type, capacity)
如果給定了一個緩沖區容量,通道就是異步的。只要緩沖區有未使用空間用于發送數據,或還包含可以接收的數據,那么其通信就會無阻塞地進行。
示例代碼:
func main() {
c := make(chan int, 3) //帶緩沖的通道
//內置函數 len 返回未被讀取的緩沖元素數量, cap 返回緩沖區大小
fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))
go func() {
defer fmt.Println("子協程結束")
for i := 0; i < 3; i++ {
c <- i
fmt.Printf("子協程正在運行[%d]: len(c)=%d, cap(c)=%d\n", i, len(c), cap(c))
}
}()
time.Sleep(2 * time.Second) //延時2s
for i := 0; i < 3; i++ {
num := <-c //從c中接收數據,并賦值給num
fmt.Println("num = ", num)
}
fmt.Println("main協程結束")
}
5. 關閉channel
如果發送者知道,沒有更多的值需要發送到channel的話,那么讓接收者也能及時知道沒有多余的值可接收將是有用的,因為接收者可以停止不必要的接收等待。這可以通過內置的close函數來關閉channel實現。
示例代碼:
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
//把 close(c) 注釋掉,程序會一直阻塞在 if data, ok := <-c; ok 那一行
close(c)
}()
for {
//ok為true說明channel沒有關閉,為false說明管道已經關閉
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("Finished")
}
注意:
- channel不像文件一樣需要經常去關閉,只有當你確實沒有任何發送數據了,或者你想顯式的結束range循環之類的,才去關閉channel;
- 關閉channel后,無法向channel 再發送數據(引發 panic 錯誤后導致接收立即返回零值);
panic: send on closed channel
- 關閉channel后,可以繼續從channel接收數據;
- 對于nil channel,無論收發都會被阻塞。
可以使用 range 來迭代不斷操作channel:
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
//把 close(c) 注釋掉,程序會一直阻塞在 for data := range c 那一行
close(c)
}()
for data := range c {
fmt.Println(data)
}
fmt.Println("Finished")
}
6. 單向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:
c := make(chan int, 3)
var send chan<- int = c // send-only
var recv <-chan int = c // receive-only
send <- 1
//<-send //invalid operation: <-send (receive from send-only type chan<- int)
<-recv
//recv <- 2 //invalid operation: recv <- 2 (send to receive-only type <-chan int)
//不能將單向 channel 轉換為普通 channel
d1 := (chan int)(send) //cannot convert send (type chan<- int) to type chan int
d2 := (chan int)(recv) //cannot convert recv (type <-chan int) to type chan int
示例代碼:
// chan<- //只寫
func counter(out chan<- int) {
defer close(out)
for i := 0; i < 5; i++ {
out <- i //如果對方不讀 會阻塞
}
}
// <-chan //只讀
func printer(in <-chan int) {
for num := range in {
fmt.Println(num)
}
}
func main() {
c := make(chan int) // chan //讀寫
go counter(c) //生產者
printer(c) //消費者
fmt.Println("done")
}
7. 生產者消費者模型
單向channel最典型的應用是“生產者消費者模型”。
所謂“生產者消費者模型”:
某個模塊(函數等)負責產生數據,這些數據由另一個模塊來負責處理(此處的模塊是廣義的,可以是類、函數、協程、線程、進程等)。產生數據的模塊,就形象地稱為生產者;而處理數據的模塊,就稱為消費者。
單單抽象出生產者和消費者,還夠不上是生產者/消費者模型。該模式還需要有一個緩沖區處于生產者和消費者之間,作為一個中介。生產者把數據放入緩沖區,而消費者從緩沖區取出數據。
那么,這個緩沖區有什么用呢?為什么不讓生產者直接調用消費者的某個函數,直接把數據傳遞過去,而畫蛇添足般的設置一個緩沖區呢?
緩沖區的好處大概如下:
1. 解耦: 生產者和消費者之間降低耦合度,任意一方修改,不會直接影響對端。
假設生產者和消費者分別是兩個類。如果讓生產者直接調用消費者的某個方法,那么生產者對于消費者就會產生依賴(也就是耦合)。將來如果消費者的代碼發生變化,可能會直接影響到生產者。而如果兩者都依賴于某個緩沖區,兩者之間不直接依賴,耦合度也就相應降低了。
2. 處理并發: 借助異步通信機制,在生產者、消費者等多個go程間實現并行通信。
生產者直接調用消費者的某個方法,還有另一個弊端。由于函數調用是同步的(或者叫阻塞的),在消費者的方法沒有返回之前,生產者只好一直等在那邊。萬一消費者處理數據很慢,生產者只能無端浪費時間。
使用了生產者/消費者模式之后,生產者和消費者可以是兩個獨立的并發主體。生產者把制造出來的數據往緩沖區一丟,就可以再去生產下一個數據。基本上不用依賴消費者的處理速度。
其實最當初這個生產者消費者模式,主要就是用來處理并發問題的。
3. 緩存: 借助緩沖區,緩存數據,從而提高生產者、消費者效率。
如果生產者制造數據的速度時快時慢,緩沖區的好處就體現出來了。當數據制造快的時候,消費者來不及處理,未處理的數據可以暫時存在緩沖區中。等生產者的制造速度慢下來,消費者再慢慢處理掉。
示例代碼:
package main
import "fmt"
// 此通道只能寫,不能讀。
func producer(out chan<- int) {
for i := 0; i < 10; i++ {
out <- i * i // 將 i*i 結果寫入到只寫channel
}
close(out)
}
// 此通道只能讀,不能寫
func consumer(in <-chan int) {
for num := range in { // 從只讀channel中獲取數據
fmt.Println("num =", num)
}
}
func main() {
ch := make(chan int) // 創建一個雙向channel
// 新建一個groutine, 模擬生產者,產生數據,寫入 channel
go producer(ch) // channel傳參, 傳遞的是引用。
// 主協程,模擬消費者,從channel讀數據,打印到屏幕
consumer(ch) // 與 producer 傳遞的是同一個 channel
}
簡單說明:首先創建一個雙向的channel,然后開啟一個新的goroutine,把雙向通道作為參數傳遞到producer方法中,同時轉成只寫通道。子協程開始執行循環,向只寫通道中添加數據,這就是生產者。主協程,直接調用consumer方法,該方法將雙向通道轉成只讀通道,通過循環每次從通道中讀取數據,這就是消費者。
注意:channel作為參數傳遞,是引用傳遞。
定時器
1. time.Timer
Timer是一個定時器。代表未來的一個單一事件,你可以告訴timer你要等待多長時間。
type Timer struct {
C <-chan Time
r runtimeTimer
}
它提供一個channel,在定時時間到達之前,沒有數據寫入timer.C會一直阻塞。直到定時時間到,向channel寫入值,阻塞解除,可以從中讀取數據。
示例代碼:
package main
import (
"fmt"
"time"
)
func main() {
//創建定時器,2秒后,定時器就會向自己的C字節發送一個time.Time類型的元素值
timer1 := time.NewTimer(time.Second * 2)
t1 := time.Now() //當前時間
fmt.Printf("t1: %v\n", t1)
t2 := <-timer1.C
fmt.Printf("t2: %v\n", t2)
//如果只是想單純的等待的話,可以使用 time.Sleep 來實現
timer2 := time.NewTimer(time.Second * 2)
<-timer2.C
fmt.Println("2s后")
time.Sleep(time.Second * 2)
fmt.Println("再一次2s后")
<-time.After(time.Second * 2)
fmt.Println("再再一次2s后")
timer3 := time.NewTimer(time.Second)
go func() {
<-timer3.C
fmt.Println("Timer 3 expired")
}()
stop := timer3.Stop() //停止定時器
if stop {
fmt.Println("Timer 3 stopped")
}
fmt.Println("before")
timer4 := time.NewTimer(time.Second * 5) //原來設置3s
timer4.Reset(time.Second * 1) //重新設置時間
<-timer4.C
fmt.Println("after")
}
定時器的常用操作:
- 實現延遲功能
<-time.After(2 * time.Second) //定時2s,阻塞2s,2s后產生一個事件,往channel寫內容
fmt.Println("時間到")
time.Sleep(2 * time.Second)
fmt.Println("時間到")
timer := time.NewTimer(2 * time.Second)
<- timer.C
fmt.Println("時間到")
- 定時器停止
timer := time.NewTimer(3 * time.Second)
go func() {
<-timer.C
fmt.Println("子協程可以打印了,因為定時器的時間到")
}()
timer.Stop() //停止定時器
for {
}
- 定時器重置
timer := time.NewTimer(3 * time.Second)
ok := timer.Reset(1 * time.Second) //重新設置為1s
fmt.Println("ok = ", ok)
<-timer.C
fmt.Println("時間到")
2. time.Ticker
Ticker是一個周期觸發定時的計時器,它會按照一個時間間隔往channel發送系統當前時間,而channel的接收者可以以固定的時間間隔從channel中讀取事件。
type Ticker struct {
C <-chan Time // The channel on which the ticks are delivered.
r runtimeTimer
}
示例代碼:
package main
import (
"fmt"
"time"
)
func main() {
//創建定時器,每隔1秒后,定時器就會給channel發送一個事件(當前時間)
ticker := time.NewTicker(time.Second * 1)
i := 0
go func() {
for { //循環
<-ticker.C
i++
fmt.Println("i = ", i)
if i == 5 {
ticker.Stop() //停止定時器
}
}
}() //別忘了()
//死循環,特地不讓main goroutine結束
for {
}
}