一個goroutine資料流任務的暫停⏸️與恢復⏯
熟悉go程式設計的同學,肯定都用過time.Sleep來暫停goroutine的執行,但是time.Sleep無法實現按照事件暫停和恢復。換句話說,你一旦設定了暫停時間,那後面的事情就由不得你了,你設了暫停10秒就是10秒,設了1分鐘就是1分鐘,而且你沒法“永遠暫停”下去。
那麼現在問題就來了,我有一個資料流的播放任務,希望做到使用者點選暫停⏸️按鈕(發出暫停訊號)的時候,播放任務被暫停(不再輸出資料),而使用者點選恢復⏯按鈕(發出恢復訊號)的時候,播放任務從被暫停的地方繼續。這個暫停和恢復可以按照使用者的意願進行無數多次,暫停多久不能預先設定,而是看使用者的心情?
關於這個問題,我在網上找了挺久,並沒有找到特別好的例子,直到看到《Go併發程式設計實戰》這本書才得到了啟示。
select 語句
select語句是一個僅能被用於傳送和接收通道中的元素值的專用語句,一個select語句在被執行的時候會根據通道的值選擇執行其中的某一個分支,通道里沒有值的時候就走default分支(前提是你寫了這麼一個default分支)
現在利用select語句,我們可以寫出控制資料流的關鍵程式碼(為方便起見,在此我們把資料流簡化為一個迴圈輸出)
// 略去一些宣告
迴圈總次數 := 10000
執行訊號 := make(chan struct{})
for i := 0; i < 迴圈總次數; {
select {
case <-執行訊號:
fmt.Printf("資料流播放到:%d\n", i)
time.Sleep(1 * time.Second) // 讓播放顯得慢一點,每次停1秒
i++
default:
continue // 暫停時保持空轉
}
}
我們要暫停資料流播放的時候,只需要讓
執行訊號 = nil
即可。
此時,名為“執行訊號”的通道里沒有值,所以select語句只會走default分支,那麼整個for迴圈就進入到了一個無限空轉的過程中,直到下一次“執行訊號”裡再有值才會繼續資料流的播放。
Task 資料流的模擬
package task // task.go
import "log"
// Task 任務結構體
type Task struct {
任務編號 string
迴圈總次數 int
是否完成 bool
工作訊號 <-chan struct{}
工作訊號備份 <-chan struct{}
}
// NewTask 新任務
func NewTask(任務編號 string, 迴圈總次數 int) *Task {
ch := make(chan struct{})
defer close(ch)
return &Task{
任務編號: 任務編號,
迴圈總次數: 迴圈總次數,
工作訊號: ch,
工作訊號備份: ch,
}
}
// Play 開始執行
func (t *Task) Play() {
log.Printf("啟動任務:%s , 次數: %d\n", t.任務編號, t.迴圈總次數)
for i := 0; i < t.迴圈總次數; { // 用迴圈模擬資料流
select {
case <-t.工作訊號:
log.Printf("任務 %s @ %d\n", t.任務編號, i)
time.Sleep(1 * time.Second) // 讓模擬資料流走得慢點
i++
default:
continue // ⚠️ 這個 continue 是Pause時執行 空轉的
}
}
t.是否完成 = true
}
// Pause 暫停
func (t *Task) Pause() {
if !t.是否完成 {
t.工作訊號 = nil
log.Printf("%s # 暫停 \n", t.任務編號)
} else {
log.Printf("%s # 已執行完畢,不能暫停 \n", t.任務編號)
}
}
// Resume 恢復執行
func (t *Task) Resume() {
if !t.是否完成 {
t.工作訊號 = t.工作訊號備份
log.Printf("%s # 恢復執行 \n", t.任務編號)
} else {
log.Printf("%s # 已執行完畢,不能恢復 \n", t.任務編號)
}
}
主程式 模擬多次暫停與恢復
package main
import (
"(...路徑)/task" // 填寫task.go 檔案所在路徑
"log"
"sync"
"time"
)
func main() {
任務1名稱 := "task #15"
t1 := task.NewTask(任務1名稱, 15) // 建立一個資料流任務
var wg sync.WaitGroup
wg.Add(5)
go func() {
defer wg.Done()
log.Println("任務啟動")
t1.Play()
log.Println("播放結束")
}()
go func() {
defer wg.Done()
time.Sleep(4 * time.Second)
log.Printf("%s 啟動4秒後,試圖暫停\n", 任務1名稱)
t1.Pause()
}()
go func() {
defer wg.Done()
time.Sleep(8 * time.Second)
log.Printf("%s 啟動暫停8秒後,試圖恢復\n", 任務1名稱)
t1.Resume()
}()
go func() {
defer wg.Done()
time.Sleep(12*time.Second)
log.Printf("%s 啟動12秒後,再次暫停 \n", 任務1名稱)
t1.Pause()
}()
go func() {
defer wg.Done()
time.Sleep(15 * time.Second)
log.Printf("%s 啟動15秒後,再次恢復\n", 任務1名稱)
t1.Resume()
}()
wg.Wait()
}
小結
以上模擬了一個最簡單的資料流的多次暫停與恢復,我們可以根據實際專案中的需要來擴充套件Task以及其呼叫。
《Go併發程式設計實戰》寫的非常細緻,Go併發的原理講得很清楚,也有很多貼近實戰的程式碼,對於想深入學習Go語言的同學來說是本很好的教程。
相關文章
- ManualResetEvent實現執行緒的暫停與恢復執行緒
- Linux中執行緒的掛起與恢復(程式暫停)Linux執行緒
- 域名暫停解析是怎麼回事?如何恢復解析?
- 04_FreeRTOS的任務掛起與恢復
- 閃回恢復一個表中的資料
- 【資料庫資料恢復】無法啟動MongoDB服務的資料恢復案例資料庫資料恢復MongoDB
- Oracle12c多租戶資料庫備份與恢復 - 恢復一個PDBOracle資料庫
- 一個錯誤的資料檔案的恢復
- 資料庫資料恢復——Windows無法啟動MongoDB服務的資料恢復案例資料庫資料恢復WindowsMongoDB
- 【資料庫資料恢復】MongoDB資料庫服務啟動失敗的資料恢復案例資料庫資料恢復MongoDB
- Android學習路線(十三)Activity生命週期——暫停和恢復(Pausing and Resuming )一個Activity...Android
- Windows10系統怎麼暫停和恢復OneDrive檔案同步Windows
- 記錄一次停電導致的資料庫不完全恢復資料庫
- 恢復資料,資料塊恢復
- Golang 入門 : 等待 goroutine 完成任務Golang
- 【伺服器資料恢復】Hyper-V服務癱瘓的資料恢復案例伺服器資料恢復
- Redis的資料備份與恢復Redis
- 資料庫的備份與恢復資料庫
- 資料庫備份與恢復----第一課資料庫
- 分散式資料庫事務故障恢復的原理與實踐分散式資料庫
- 資料恢復:AMDU資料抽取恢復資料恢復
- 備份與恢復:polardb資料庫備份與恢復資料庫
- Oracle備份與恢復【丟失資料檔案的恢復】Oracle
- Oracle資料庫備份與恢復--Windows批處理+定時任務計劃(V1.0)Oracle資料庫Windows
- 【資料庫資料恢復】透過恢復NDF檔案修復資料庫的資料恢復過程資料庫資料恢復
- 最近恢復了一個8T資料量的資料庫資料庫
- Mysql資料備份與恢復MySql
- mysqldump 恢復單個資料庫MySql資料庫
- IPTelecom部署甲骨文零資料丟失恢復一體機,助力資料保護與恢復即服務業務發展
- Facebook同意在英國暫停WhatsApp資料共享APP
- Oracle資料泵的備份與恢復Oracle
- mysql的資料庫備份與恢復MySql資料庫
- SCN與資料庫恢復的關係資料庫
- oracle資料庫的備份與恢復Oracle資料庫
- goroutine併發執行多個任務並依次返回結果Go
- win10印表機暫停了怎麼恢復列印 電腦印表機暫停後怎麼繼續列印Win10
- 備份與恢復--一個表空間能否被多個資料庫讀寫?資料庫
- 通過事務日誌恢復SqlServer資料庫到一個特定的時間點SQLServer資料庫