1. goroutine
go關鍵字實現的最簡單的併發, 注意程序會在main函數結束時退出程序,並不會等待其他goroutin結束。
package main import ( "fmt" "time" ) func Add(x, y int) { z := x + y fmt.Println(z) } func main() { x := 1 y := 1 for i:=0; i<10 ;i++ { go Add(x, y) } time.Sleep(4) }
2.併發通信
併發通信通過 共享數據 和 消息來實現
package main import ( "fmt" "runtime" "sync" ) var counter = 0 func Add(lock *sync.Mutex) { lock.Lock() counter += 1 lock.Unlock() } func main() { lock := &sync.Mutex{} for i:=0 ; i<10 ; i++ { go Add(lock) } for { lock.Lock() c := counter lock.Unlock() runtime.Gosched() // 讓出cpu的時間片,程序遇到goshed就好讓出cpu讓給其他goroutine執行 if c >= 10{ break } } fmt.Println(counter) }
3. chanel
package main import "fmt" func Count(count *int, ch chan int) { *count = *count + 1 fmt.Println(*count) ch <- 1 // 想channel 中寫入數據,在channel中的數據被讀取之前,這個操作是阻塞的 } func main() { count := 0 chs := make([]chan int, 5) for i:=0;i<5;i++{ chs[i] = make(chan int) go Count(&count,chs[i]) } for _, ch := range chs{ <- ch // 從channel中讀取數據,在數據被寫入之前,這個操作是 } }
3.1 基本語法
// 聲明 var chanName chan ElementType //在類型之前加了一個chan關鍵字 var ch chan int var m map[string] chan bool // 定義 ch = make(chan int) // 初始化一個int型的channel //channel 的讀寫 ch <- value // 將一個數據發送至channel, 寫入數據會產生阻塞,知道其他grountine從channel中將數據讀走 varlue = <-ch // 從channel讀取數據,如果channel中沒數據也會導致阻塞,知道有數據寫入
package main import "fmt" func Counsumer(ch1, ch2 chan int) { var value int for { value = <-ch1 if value >= 100{ ch2 <- 0 } fmt.Println("消費了", value) } } func main() { ch1 := make(chan int) ch2 := make(chan int) go Counsumer(ch1, ch2) var i int = 0 for i < 100{ i += 1 ch1 <- i fmt.Println("生產了", i) } <- ch2 }
3.2 select
select 語法與switch相似,只不過每一個case條件必須是一個channel操作。
package main import "fmt" func main() { ch := make(chan int, 1) j := 0 for j < 100{ j += 1 select { case ch <- 0: case ch <- 1: } i := <-ch fmt.Println(i) } }
執行上面代碼會發現打印的數字有時爲1有時爲0。這與select的機制有關,它是隨機的。
3.3 緩衝機制
package main import "fmt" func Consumer(ch chan int) { for{ value := <-ch fmt.Println("消費了", value) } } func main() { ch := make(chan int, 100) // 構造channel的時候可以指定channel的緩存大小 i := 0 // 設置緩存大小後,如果chan沒滿則生產環節不再阻塞 go Consumer(ch) for i < 100{ i += 1 print("生產", i, "\n") ch <- i } }
帶緩衝區的channel還可以這樣讀
package main import ( "fmt" ) func Consumer(ch, ch2 chan int) { Loop1: for { //value := <-ch for value := range ch{ fmt.Println("消費了", value) if value >= 100{ break Loop1 } } } fmt.Println("lalala") ch2 <- 0 } func main() { ch := make(chan int, 100) ch2 := make(chan int) i := 0 go Consumer(ch, ch2) for i < 100{ i += 1 print("生產", i) ch <- i } <-ch2 }
3.4超時機制
package main import ( "fmt" "time" ) func main() { timeout := make(chan int, 1) func() { time.Sleep(1) timeout <- 1 }() ch := make(chan int, 1) ch <- 1 t := time.Now() select{ case <-ch: fmt.Println("從ch中讀到數據") case <-timeout: fmt.Println("從timeout讀取數據") } fmt.Println(time.Now().Sub(t)) }
3.5 channel的傳遞
chanel本身也是原生類型,與map之類的類型一樣,channel定義之後也可以通過channel傳遞
package main type Pipe struct { value int handler func(i int) int next chan int } func handle(queue chan *Pipe) { for data := range queue{ data.next <- data.handler(data.value) } } func add(i int)int { return i + 1 } func main() { var p1 Pipe = Pipe{1, add, make(chan int,1)} }
3.6 單向channel
var ch1 chan int // 正常的channel var ch2 chan<- float64 // ch2是單向channel, 只能用於寫float64數據 var ch3 <-chan int // 只能用於讀取int數據 ch4 := make(chan int) ch5 := <-chan int(ch4) // 將ch4轉換爲單向讀取channel ch6 := chan<- int(ch4) // 將ch4轉換爲單向寫channel // 用法 func Parse(ch <-chan int) { for value := range ch{ // 在這個函數中只能對ch進行讀操作 fmt.Println(value) } }
package main import ( "fmt" "time" ) func read(ch <-chan int) { value := <-ch fmt.Println(value) } func main() { ch4 := make(chan int, 1) go read(ch4) ch4 <- 1 time.Sleep(1e9) }
3.7 關閉channel
close(ch)
3.8 出讓時間片
runtime.Gosched() // 如果要精細的控制goroutine的行爲,就必須深入的瞭解runtime包提供的具體功能
3.9 同步
3.9.1同步鎖:
sync.Mutex
sync.RWmutex 單寫多讀模型,一個goroutine獲取讀鎖之後,只限制寫,其他goroutine仍可以讀。而寫鎖會組織所有goroutine獲取讀鎖和寫鎖
Lock 寫鎖
RLock 讀鎖
var l sync.Mutex func foo() { l.Lock() defer l.Unlock() }
3.9.2全局唯一性操作
先來看一段代碼
package main import "fmt" var done bool = false func setup() { fmt.Println("hello, world") done = true } func doprint() { if !done{ setup() } }
這段代碼的目的是保證setup函數只被執行一次。但是細看還是會有問題,因爲setup並不是一個原子性操作, 這種寫法可能會導致setup函數被多次調用。