Golang - 並發編程
1. 並行和並發
- 並行:在同一時刻,有多條指令在多個CPU處理器上同時執行
- 2個隊伍,2個窗口,要求硬件支持
- 並發:在同一時刻,只能有一條指令執行,但多個進程指令被快速地輪換執行
- 2個隊伍,1個窗口,要求提升軟件能力
2. go語言並發優勢
- go從語言層面就支持了並發
- 簡化了並發程序的編寫
3. goroutine是什么
- 它是go並發設計的核心
- goroutine就是協程,它比線程更小,十幾個goroutine在底層可能就是五六個線程
- go語言內部實現了goroutine的內存共享,執行goroutine只需極少的棧內存(大概是4~5KB)
4. 創建goroutine
-
只需要在語句前添加go關鍵字,就可以創建並發執行單元
package main
import (
"fmt"
"time"
)//測試協程
//循環打印內容
func newTask() {
i := 0
for {
i++
fmt.Printf("new goroutine:i=%d\n", i)
time.Sleep(1 * time.Second)
}
}//main()相當於是主協程
func main() {
//啟動子協程
go newTask()
i := 0
for {
i++
fmt.Printf("main goroutine:i=%d\n", i)
time.Sleep(1 * time.Second)
}
} -
開發⼈員無需了解任何執⾏細節,調度器會自動將其安排到合適的系統線程上執行
-
如果主協程退出了,其他任務還執行嗎?不執行
package main import ( "fmt" "time" ) //main()相當於是主協程 func main() { //匿名子協程 go func() { i := 0 for { i++ fmt.Println("子協程 i=", i) time.Sleep(1 * time.Second) } }() i := 0 for { i++ fmt.Println("主協程 i=", i) time.Sleep(1 * time.Second) //主協程第二次后退出 if i == 2 { break } } } -
程序沒任何輸出,也不報錯
package main import ( "fmt" "time" ) //main()相當於是主協程 func main() { //匿名子協程 go func() { i := 0 for { i++ fmt.Println("子協程 i=", i) time.Sleep(1 * time.Second) } }() }
5. runtime包
-
runtime.Gosched():用於讓出CPU時間片,調度器重新安排任務調度,還是有幾率分配到它的
package main import ( "fmt" "runtime" ) func main() { //匿名子協程 go func(s string) { for i := 0; i < 2; i++ { fmt.Println(s) } }("world") //主協程 for i := 0; i < 2; i++ { runtime.Gosched() fmt.Println("hello") } } -
runtime.Goexit():立即終止當前協程
package main import ( "fmt" "time" "runtime" ) func main() { //匿名子協程 go func() { defer fmt.Println("A.defer") //匿名函數 func() { defer fmt.Println("B.defer") //此時只有defer執行 runtime.Goexit() fmt.Println("B") }() fmt.Println("A") }() for { time.Sleep(time.Second) } } -
runtime.GOMAXPROCS():設置並行計算的CPU核數,返回之前的值
package main import ( "runtime" "fmt" ) func main() { n := runtime.GOMAXPROCS(3) fmt.Println("n=%d\n",n) //循環執行2個 for{ go fmt.Print(0) fmt.Print(1) } }
6. channel是什么
- goroutine運行在相同的地址空間,因此訪問共享內存必須做好同步,處理好線程安全問題
- goroutine奉行通過通信來共享內存,而不是共享內存來通信
- channel是一個引用類型,用於多個goroutine通訊,其內部實現了同步,確保並發安全
7. channel的基本使用
-
channel可以用內置make()函數創建
-
定義一個channel時,也需要定義發送到channel的值的類型
make(chan 類型) //無緩沖的通道 make(chan 類型, 容量) //有緩沖的通道 -
當 capacity= 0 時,channel 是無緩沖阻塞讀寫的,當capacity> 0 時,channel 有緩沖、是非阻塞的,直到寫滿 capacity個元素才阻塞寫入
-
channel通過操作符<-來接收和發送數據,發送和接收數據語法:
channel <- value //發送value到channel <-channel //接收通道數據,並丟棄 x := <-channel //通道取值並賦給x x, ok := <-channel //ok是檢查通道是否關閉或者是否為空 -
channel基本使用
package main import "fmt" func main() { //創建存放int類型的通道 c := make(chan int) //子協程 go func() { defer fmt.Println("子協程結束") fmt.Println("子協程正在運行...") //將666發送到通道c c <- 666 }() //若已取出數據,下面再取會報錯 //<-c //主協程取數據 //從c中取數據 num := <-c fmt.Println("num = ", num) fmt.Println("主協程結束") }
8. 無緩沖的channel
- 無緩沖的通道是指在接收前沒有能力保存任何值的通道
- 無緩沖通道,有可能阻塞
發送者 -> (通道(有可能有數據阻塞)) -> 接受者
package main
import (
"fmt"
"time"
)
func main() {
//創建無緩沖通道
c := make(chan int, 0)
//長度和容量
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)
//主協程取數據
for i := 0; i < 3; i++ {
num := <-c
fmt.Println("num=", num)
}
fmt.Println("主協程結束")
}
9. 有緩沖的channel
- 有緩沖的通道是一種在被接收前能存儲一個或者多個值的通道
發送者 -> (通道(數據),(數據)(...)) -> 接受者
-
上面代碼創建時修改容量即可
//創建有緩存的通道 c :=make(chan int, 3)
10. close()
-
可以通過內置的close()函數關閉channel
package main import "fmt" func main() { //創建通道 c := make(chan int) //子協程存數據 go func() { for i := 0; i < 5; i++ { c <- i } //子協程close close(c) }() //主協程取數據 for { if data, ok := <-c; ok { fmt.Println(data) } else { break } } fmt.Println("Finshed") } -
也可以如下遍歷
for data := range c{ fnt.Println(data) }
11. 單方向的channel
-
默認情況下,通道是雙向的,也就是,既可以往里面發送數據也可以接收數據
-
go可以定義單方向的通道,也就是只發送數據或者只接收數據,聲明如下
var ch1 chan int //正常的
var ch2 chan<- float64 //單向的,只用於寫float64的數據
var ch3 <-chan int //單向的,只用於讀取int數據 -
可以將 channel 隱式轉換為單向隊列,只收或只發,不能將單向 channel 轉換為普通channel
func main() {
//創建通道
c := make(chan int, 3)
//1. 將c准換為只寫的通道
var send <- chan int =c
//2. 將c轉為只讀的通道
var recv <- chan int =c
//往send里面寫數據
send < -1
//從recv讀數據
<-recv
}
-
單方向的channel有什么用?模擬生產者和消費者
package main import "fmt" //生產者,只寫 func producter(out chan<- int) { //關閉資源 defer close(out) for i := 0; i < 5; i++ { out <- i } } //消費者,只讀 func consumer(in <-chan int) { for num := range in { fmt.Println(num) } } func main() { //創建通道 c := make(chan int) //生產者運行,向管道c存數據 go producter(c) //消費者運行 consumer(c) fmt.Println("done") }
12. 定時器
-
Timer:定時,時間到了響應一次
package main import ( "time" "fmt" ) func main() { //1.基本使用 //創建定時器 //2秒后,定時器會將一個時間類型值,保存向自己的c //timer1 := time.NewTimer(2 * time.Second) ////打印當前時間 //t1 := time.Now() //fmt.Printf("t1:%v\n", t1) ////從管道中取出C打印 //t2 := <-timer1.C //fmt.Printf("t2:%v\n", t2) //2.Timer只響應一次 //timer2 := time.NewTimer(time.Second) //for { // <-timer2.C // fmt.Println("時間到") //} //3.通過Timer實現延時的功能 ////(1)睡眠 //time.Sleep(2*time.Second) //fmt.Println("2秒時間到") ////(2)通過定時器 //timer3 := time.NewTimer(2 * time.Second) //<-timer3.C //fmt.Println("2秒時間到") ////(3)After() //<-time.After(2 * time.Second) //fmt.Println("2秒時間到") //4.停止定時器 //timer4 := time.NewTimer(3 * time.Second) ////子協程 //go func() { // <-timer4.C // fmt.Println("定時器器時間到,可以打印了") //}() //stop := timer4.Stop() //if stop { // fmt.Println("timer4已關閉") //} //5.重置定時器 timer5 := time.NewTimer(3 * time.Second) //定時器改為1秒 timer5.Reset(1 * time.Second) fmt.Println(time.Now()) fmt.Println(<-timer5.C) for { } } -
Ticker:響應多次
package main
import (
"time"
"fmt"
)func main() {
//創建定時器,間隔1秒
ticker := time.NewTicker(time.Second)i := 0 //子協程 go func() { for { <-ticker.C fmt.Println(<-ticker.C) i++ fmt.Println("i=", i) //停止定時器 if i == 5 { ticker.Stop() } } }() //死循環 for { }}
13. select
-
go語言提供了select關鍵字,可以監聽channel上的數據流動
-
語法與switch類似,區別是select要求每個case語句里必須是一個IO操作
select { case <-chan1: // 如果chan1成功讀到數據,則進行該case處理語句 case chan2 <- 1: // 如果成功向chan2寫入數據,則進行該case處理語句 default: // 如果上面都沒有成功,則進入default處理流程 } package main import ( "fmt" ) func main() { //創建數據通道 int_chan := make(chan int, 1) string_chan := make(chan string, 1) //創建2個子協程,寫數據 go func() { //time.Sleep(2 * time.Second) int_chan <- 1 }() go func() { string_chan <- "hello" }() //如果都能匹配到,則隨機選擇一個去跑 select { case value := <-int_chan: fmt.Println("intValue:", value) case value := <-string_chan: fmt.Println("strValue:", value) } fmt.Println("finish") }
14. 攜程同步鎖
-
go中channel實現了同步,確保並發安全,同時也提供了鎖的操作方式
-
go中sync包提供了鎖相關的支持
-
Mutex:以加鎖方式解決並發安全問題
package main import ( "time" "fmt" "sync" ) //賬戶 type Account struct { money int flag sync.Mutex } //模擬銀行檢測 func (a *Account)Check() { time.Sleep(time.Second) } //設置賬戶余額 func (a *Account)SetAccount(n int) { a.money +=n } //查詢賬戶余額 func (a *Account)GetAccount() int{ return a.money } //買東西1 func (a *Account)Buy1(n int){ a.flag.Lock() if a.money>n{ //銀行檢測 a.Check() a.money -=n } a.flag.Unlock() } //買東西2 func (a *Account)Buy2(n int){ a.flag.Lock() if a.money>n{ //銀行檢測 a.Check() a.money -=n } a.flag.Unlock() } func main() { var account Account //設置賬戶余額 account.SetAccount(10) //2個子協程買東西 go account.Buy1(6) go account.Buy2(5) time.Sleep(2 * time.Second) fmt.Println(account.GetAccount()) } -
sync.WaitGroup:用來等待一組子協程的結束,需要設置等待的個數,每個子協程結束后要調用Done(),最后在主協程中Wait()即可
-
引入
package main import ( "fmt" ) func main() { //創建通道 ch := make(chan int) //count表示活動的協程個數 count := 2 go func() { fmt.Println("子協程1") //子協程1執行完成,給通道發送信號 ch <-1 }() go func() { fmt.Println("子協程2") ch <-1 }() //time.Sleep(time.Second) //從ch中不斷讀數據 for range ch{ count -- if count == 0{ close(ch) } } } -
go提供了這種解決方案sync.WaitGroup
-
Add():添加計數
-
Done():操作結束時調用,計數減去1
-
Wait():主函數調用,等待所有操作結束
未完待續...
