Yemilice

博客已经迁移:https://www.yemilice.com/ 文章不会再双更新,感谢多年陪伴,新博再见。

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

消失这么久的原因

疫情太严重,哥们本来打算在新疆滑雪+吃烤肉度过一个美好的假期,结果没成想给困那里了,这不就尴尬了么,这不,博客没更新,现在我又回来了,哈哈哈哈!

我要实现个什么玩意儿

有一个需求,简单的说就是我要写一个任务管理框架,主要功能有任务开启,任务关闭,任务监控等等。说的抽象点,就是我要用Golang写一个任务管理的功能,任务很可能有多个,并且我想停任务就停任务,想开始任务我就开始任务!我不要你觉得,我要我觉得!

为什么我要用Golang

众所周知,golang这东西,有个黑科技,叫goroutine,这东西很牛逼,牛逼在哪儿呢?简单的说,快,小,短!协程切换快,占用资源少,并且,异步的,可以开多个,直接一个go 关键字就给人家打开了,多棒!

实际需求分析

结合我们上面的任务需求,实现一个任务开始,任务停止的逻辑,这就说明,任务肯定不只有一个,并且任务都在后台,我们该怎么去监控,或者去管理这个go任务,或者go函数呢,golang提供了很多解决办法,例如WaitGroup,context等方法。
思考一下,我们的这个需求,任务都是跑在后台的异步并发逻辑,这就说明不只一个任务会被启动和停止,这样对我们的任务管理是一个很大的挑战,因为任务都是在后台隐秘执行的,如果是一般逻辑,我们要停止任务,首先要找到任务的pid,然后kill任务进程,这是一个完整的结束任务的流程。
回到我们这个需求,基本的流程就是:

发送一个任务请求(开启任务/停止任务) -> 接收到任务请求 -> 执行任务请求

思考一下,如果,我们开启任务之后,任务进入后台,那么,我们在停止任务的时候,怎么保证,能够找到这个任务,精准的打击(停止)它呢?看了题目你应该就知道了,用context就好了。下面我就来介绍一下它吧。

主角context介绍

网上很多博客介绍它,我粗粗看了一眼,非常抽象,很多人再一描述,就更麻烦更抽象了。我这里不说的太麻烦,简单描述一下,这个东西context,是干嘛呢,你们理解一下株连,连坐这两个词汇,这个东西相当于就是锁链,铁锁连舟,不进则退,说明白点,就是一个串连上下文的类似信号传递的玩意儿。每个调用链上的函数都要以它作为函数进行传递,举个例子,"株九族"这个词,是因为一个人犯罪,结果家人都因为他被砍了头,这个犯罪的人,就是父context,其他因为他被杀的人,就是子context,还可能有孙context,他被砍头了,其他人也得跟着一起死,用代码表示一波

// 这是儿子函数
func gen(ctx context.Context) <-chan int {
	dst := make(chan int)
	n := 1
	go func() {
		for {
			select {
            // 接收到爹挂了的消息
			case <-ctx.Done():
                fmt.Println("儿子被砍头了。")
                // 退出任务
				return
			case dst <- n:
				n++
				time.Sleep(time.Second * 1)
			}
		}
	}()
	return dst
}
// 这是爹函数
fun test() {
    ctx, cancel := context.WithCancel(context.Background())
	// 让我造个儿子,给我儿子传个ctx
    intChan := gen(ctx)
    // 我被干了,cancel是结束
    defer Cancel()
}

这下说的明白了么?其实context还有很多别的,例如timeout之类的,但是那个是我后面准备写的,这一节就不写这些了。

实现我们的需求

我们的武器context已经准备好了,大概的使用逻辑我们也明白了,现在你们可以看到,我们只要拿到主函数(爹函数)的ctx和cancel,我们就可以控制子函数(儿子函数)的死活,我们在开发当中,任务的状态是不断在变化的,一个爹对应一个儿子,但是可能有多个任务,多个任务我们该怎么管理它?
在一般的开发任务中,我们习惯将任务记录到数据库当中,然后在开发当中不停的遍历数据库,去判断任务的状态到底是开启还是停止,这里我们要考虑到,频繁遍历数据库,会不会带来大量的访问堆积?还是否有别的解决办法?

我的解决方案

这次开发中,我选择定义一个全局变量的主map,并且定义一个任务的struct类型,代码如下

// 全局map
var jobmap = make(map[string]interface{})
//Job 任务
type Jobs struct {
	ID     string
	Status int
	Ctx    context.Context
	Cancel context.CancelFunc
}

然后我选择在任务开始的时候(创造儿子的时候),将信息填充,修改test代码如下

func test() {
	var jobs Jobs
    ctx, cancel := context.WithCancel(context.Background())
    // 造一个儿子
    intChan := gen(ctx)
    // 任务开始了
    fmt.Println("start job")
    // 重要的东西传进去
	jobs.Status = 1
	jobs.Cancel = cancel
    jobs.Ctx = ctx
    // 定义一个任务id,这个可以用uuid,或者随便整个别的
	jobs.ID = "sdads"
    m1["sdads"] = jobs
    // 阻塞任务,假装任务执行很久
	for n := range intChan {
		fmt.Println(n)
		if n == 1000 {
			break
		}
	}

再然后,我选择写一个停止函数(砍头函数)

func stopGetmi(id string) {
	//把任务停掉
	fmt.Println("stop jobs")
    jobss := m1[id]
    //interface 转 struct
    op, ok := jobss.(Jobs)
    // 调用砍头函数cancel
	defer op.Cancel()
}

进行测试

func main() {
	go test()
	go stopGetmi("sdads")
	time.Sleep(time.Second * 200)
}

发现任务执行结果

这样你就完成了干掉老爹,也干掉儿子的素质操作。

总结

主要是context的基础和说明,其实context我还是推荐大家去看看原版,我这里写的太过于简单,不过这篇博客,也是我记录一下自己开发中遇到的难题,当时看网上没有类似的说明,于是写了这篇博客,希望大家多多包涵。
祝大家都能躲过瘟疫,我们终究会在春花花开的地方相见。

posted on 2020-03-20 17:01  Yemilice  阅读(799)  评论(0编辑  收藏  举报