一个goroutine数据流任务的暂停⏸️与恢复⏯

熟悉go编程的同学,肯定都用过time.Sleep来暂停goroutine的执行,但是time.Sleep无法实现按照事件暂停和恢复。换句话说,你一旦设定了暂停时间,那后面的事情就由不得你了,你设了暂停10秒就是10秒,设了1分钟就是1分钟,而且你没法“永远暂停”下去。

那么现在问题就来了,我有一个数据流的播放任务,希望做到用户点击暂停⏸️按钮(发出暂停信号)的时候,播放任务被暂停(不再输出数据),而用户点击恢复⏯按钮(发出恢复信号)的时候,播放任务从被暂停的地方继续。这个暂停和恢复可以按照用户的意愿进行无数多次,暂停多久不能预先设定,而是看用户的心情😄

关于这个问题,我在网上找了挺久,并没有找到特别好的例子,直到看到《Go并发编程实战》这本书才得到了启示。

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"
    "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语言的同学来说是本很好的教程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343