併發技術4:同步排程
等待組
- 在此之前,我們讓主協程等待子協程結束的方式都是睡眠,睡足子協程需要的時間,這種方式顯然是不理想的!
- 等待組(sync.WaitGroup)的原理是:每增加一個子協程,就向等待組中+1,每結束一個協程,就從等待組中-1,主協程會阻塞等待直到組中的協程數等於0為止;
- 這種方式可以令主協程恰好結束在最後一個子協程結束的時間點上,Perfect!
互斥鎖案例1
- 在很多情境中,資料是不允許併發修改的;
- 典型的案例如銀行賬戶,銀行卡在存取的過程中,存摺是不允許在同一時間進行存取操作的,例如卡剛剛取走500,在查詢餘額時恰好存摺又存入500,銀行卡在查詢餘額時會誤以為銀行並沒有扣款,這顯然是應該避免的!
- 所以我們不允許銀行卡和存摺併發地執行存取操作,必須同步序列有先後地執行存取,這樣才不會帶來髒讀和幻讀;
- 我們可以通過搶互斥鎖(sync.Mutex)的方式來強制存取操作同步;
- 互斥鎖的原理是:對於有必要強制同步序列的任務,我們規定它只有得到互斥鎖才有執行權,而全域性只有一把互斥鎖,誰先搶到誰就獲得任務執行權,任務進行的過程中如果有其它協程想要得到執行權,它必須阻塞等待至當前任務協程釋放同步鎖;
- 下面的例子中,銀行卡無論誰先搶到資源鎖,都立刻對同步鎖進行鎖定(mt.Lock()),在其存取操作沒有結束之前,另一方必須阻塞等待直至前者將互斥鎖釋放(mt.Unlock());
package main
import (
"fmt"
"time"
"sync"
)
func main() {
//必須保證併發安全的資料
type Account struct {
money float32
}
var wg sync.WaitGroup
account := Account{1000}
fmt.Println(account)
//資源互斥鎖(誰搶到鎖,誰先訪問資源,其他人阻塞等待)
//全域性就這麼一把鎖,誰先搶到誰操作,其他人被阻塞直到鎖釋放
var mt sync.Mutex
//銀行卡取錢
wg.Add(1)
go func() {
//拿到互斥鎖
mt.Lock()
//加鎖的訪問
fmt.Println("取錢前:",account.money)
account.money -= 500
time.Sleep(time.Nanosecond)
fmt.Println("取錢後:",account.money)
wg.Done()
//釋放互斥鎖
mt.Unlock()
}()
//存摺存錢
wg.Add(1)
go func() {
//拿到互斥鎖(如果別人先搶到,則阻塞等待)
mt.Lock()
fmt.Println("存錢前:",account.money)
account.money += 500
time.Sleep(time.Nanosecond)
fmt.Println("存錢後:",account.money)
wg.Done()
//釋放互斥鎖
mt.Unlock()
}()
wg.Wait()
}
互斥鎖案例2
- 在上面的例子中,銀行卡和存摺的存取操作,必須強制同步,否則會形成資料的髒讀或幻讀;
- 但如果是查詢上個月的銀行流水或者僅僅是查詢使用者名稱之類的只讀操作,則沒有強制同步的必要,完全可以併發執行;
- 於是我們對上面的例子稍作修改,使得對銀行賬戶的強制同步僅限於存取操作,而對於其他操作則放開許可權令其可以被併發地執行;
- 原理很簡單:沒有必要強制同步的任務,不去搶互斥鎖就是了——需要確保同步的任務就先搶鎖後執行,其餘的則不去搶鎖,直接執行;
package main
import (
"sync"
"fmt"
"time"
)
//必須保證併發安全的資料
type Account struct {
name string
money float32
//定義該資料的互斥鎖
mt sync.Mutex
}
//本方法不能被併發執行——併發安全的
func (a *Account) saveGet(amount float32) {
//先將資源鎖起來
a.mt.Lock()
//執行操作
fmt.Println("操作前:", a.money)
a.money += amount
fmt.Println("操作後:", a.money)
<-time.After(3 * time.Second)
//釋放資源
a.mt.Unlock()
}
//本方法可以被併發執行——不是併發安全的,無此必要
func (a *Account) getName() string {
return a.name
}
func main() {
a := Account{name: "張全蛋", money: 1000}
var wg sync.WaitGroup
wg.Add(1)
go func() {
//呼叫一個加鎖的方法(同步)
a.saveGet(500)
wg.Done()
}()
wg.Add(1)
go func() {
//呼叫一個加鎖的方法(同步)
a.saveGet(-500)
wg.Done()
}()
for i:=0;i<3 ;i++ {
wg.Add(1)
go func() {
//呼叫一個普通的沒有訪問鎖的方法(非同步)
fmt.Println(a.getName())
wg.Done()
}()
}
wg.Wait()
}
通過訊號量控制併發數
- 控制併發數屬於常用的排程;
- 我們的做法是:規定併發執行的任務都必須先在某個監視管道中進行註冊,而這個監視管道的快取能力是固定的,比如說5,那麼註冊在該管道中的併發能力就是5;
package main
import (
"fmt"
"time"
"sync"
)
/*訊號量:通過控制管道的“頻寬”(快取能力)控制併發數*/
func main() {
//定義訊號量為5“頻寬”的管道
sema = make(chan int, 5)
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(index int) {
ret := getPingfangshu(index)
fmt.Println(index, ret)
wg.Done()
}(i)
}
wg.Wait()
}
//該函式只允許5併發執行
var sema chan int
func getPingfangshu(i int) int {
sema <- 1
<-time.After(2 * time.Second)
<- sema
return i
}
學院Go語言視訊主頁
https://edu.csdn.net/lecturer/1928
[清華團隊帶你實戰區塊鏈開發]
(https://ke.qq.com/course/344443?tuin=3d17195d)
掃碼獲取海量視訊及原始碼 QQ群:721929980
相關文章
- 併發技術中同步
- 併發技術2:多協程
- 併發技術4:讀寫鎖
- Java併發技術05:傳統執行緒同步通訊技術Java執行緒
- TiDB 技術內幕 - 談排程TiDB
- Go 併發程式設計 - runtime 協程排程(三)Go程式設計
- 高併發技術
- 如何快速排程 PTS 的百萬併發能力
- 併發技術1:CSP併發理論
- 2. Go併發程式設計--GMP排程Go程式設計
- GO-併發技術Go
- 技術分享| 淺談排程平臺設計
- 執行緒,程式,協程, 併發,並行,同步,非同步概念解析執行緒並行非同步
- 技術分享| 快對講影片排程功能說明
- 技術分享| 快對講綜合排程系統
- 技術分享| 應急指揮排程平臺需要這些技術支撐
- Celery非同步排程框架(一)基本使用非同步框架
- 技術分享| 排程平臺的好助手-快對講
- 技術分享| 融合排程中的廣播功能設計
- 技術分享| 快對講排程系統設計概要
- 併發技術3:定時器定時器
- 併發技術3:管道通訊
- 高併發設計技術方案
- 【Go併發程式設計】第一篇 - Goroutines排程Go程式設計
- 【Go併發程式設計】第一篇 – Goroutines排程Go程式設計
- 【併發技術04】執行緒技術之死鎖問題執行緒
- 【併發技術03】傳統執行緒互斥技術—synchronized執行緒synchronized
- 技術解讀 | SD-WAN的多樣性策略排程
- 併發技術5:死鎖問題
- ?【Java技術專區】「併發程式設計專題」教你如何使用非同步神器CompletableFutureJava程式設計非同步
- Flink排程之排程器、排程策略、排程模式模式
- 【併發技術02】傳統執行緒技術中的定時器技術執行緒定時器
- 深度解讀昇騰CANN模型下沉技術,提升模型排程效能模型
- 直播CDN排程技術關鍵挑戰與架構設計架構
- javascript 實現一個帶併發限制的非同步排程器,保證同時最多執行2個任務JavaScript非同步
- celery 與 flask 實現非同步任務排程Flask非同步
- golang實現併發爬蟲三(用佇列排程器實現)Golang爬蟲佇列
- Java併發(9)- 從同步容器到併發容器Java