go的context和waitgroup简单实例

引言

最近在看go底层的源码,看到context这个包,想起这个东西大家用的挺多的,就打算写个demo,加深下理解。网上有具体讲context和waitGroup用法的,我这篇文章不会介绍它们提供的函数的功能,本文是在大家理解了这些函数的基础上,通过实战来更进一步加深印象,如果对它们提供的函数有所不理解的地方,还请自行百度了解。

个人对context的看法

context它解决了上游的事件取消能够通知到下游,并且上下游之间互不影响,下游的程序是否继续执行完全取决于自身,而上游也不需要拥有对下游的控制权,实现上下游的解耦。

但是这种设计还是有一些不方便之处。比如上层无法感知下层的状态,必须要自己手动写一个“监听”来获得下层的状态,比如一个任务会分为几个子任务,而只有当子任务全部完成时,该任务才算完成,但我用context就无法获得子任务的状态,因此我必须要使用一些“监听”手段来获得这些子任务的状态,从而判断该任务是否完成。

第二点是上层对于下层的管理太过“严格”,比如我只是想通知下一层的任务可以结束,但是context的消息却让我的下一层的下一层也收到了可以结束的通知,这对于我想法是背离的,因为对于我的子任务,我的想法是要么不做,要么做完,而不是在中途就放弃掉。不过我们可以在子任务下重新创建一个context根节点,这样该子任务的下一层就和上一层没有关系了,只是在结构上就变得复杂了,容易犯错。

解决问题一,如何上层获得下层状态

// 收拾桌子
func ClearTable(cxt context.Context, top string, testCall chan struct{}) {
	defer func() {
		fmt.Println(top, "跑路,回家玩游戏咯~")
	}()
	index := 0
	for {
		select {
		case <-cxt.Done():
			fmt.Println(top, "老板说叫我滚蛋")
			return
		default :
			if index >= 5{
				testCall <- struct{}{}
				return
			}
			index++
			fmt.Println(top, fmt.Sprintf("擦完%v张桌子", index))
			time.Sleep(time.Second * 1)
		}
	}
}

// 摆放椅子
func ClearUpChair(cxt context.Context, top string, testCall chan struct{}) {
	defer func() {
		fmt.Println(top, "跑路,回家看电视咯~")
	}()
	index := 0
	for {
		select {
		case <-cxt.Done():
			fmt.Println(top, "老板说叫我滚蛋")
			return
		default :
			if index >= 5{
				testCall <- struct{}{}
				return
			}
			index++
			fmt.Println(top, fmt.Sprintf("整理%v张椅子", index))
			time.Sleep(time.Second * 2)
		}
	}
}

// 拖地
func MopTheFloor(cxt context.Context, top string, testCall chan struct{}) {
	defer func() {
		fmt.Println(top, "跑路,回家织毛衣咯~")
	}()
	index := 0
	for {
		select {
		case <-cxt.Done():
			fmt.Println(top, "老板说叫我滚蛋")
			return
		default :
			if index >= 1{
				testCall <- struct{}{}
				return
			}
			index++
			fmt.Println(top, "开始拖地板")
			time.Sleep(time.Second * 10)
		}
	}
}


func main(){
	defer func() {
		fmt.Println("餐馆忙碌的一天结束。")
	}()
	rootCxt := context.Background()
	fatherCxt, cancel:= context.WithCancel(rootCxt)

	progressNum := 0
	progress := make(chan struct{}, 3)
	defer close(progress)

	go ClearTable(fatherCxt, "【员工A】", progress)
	go ClearUpChair(fatherCxt, "【员工B】", progress)
	go MopTheFloor(fatherCxt, "【员工C】", progress)

	timeTick := time.NewTimer(time.Second * 11)
	defer timeTick.Stop()
	for {
		select {
		case <- progress:
			progressNum++
			if progressNum >= 3{
				fmt.Println("不错不错,小伙子们都挺努力的。")
				goto END	// 这里直接跳出for循环
			}
		case <- timeTick.C:
			fmt.Println("没做完工作明天不用来上班了。")
			cancel() // 通知子任务,可以结束了
			goto END
		}
	}
END:
	time.Sleep(time.Second)	// 等一下其他协程执行完程序
}

我们使用下WithTimeout函数,这样就可以解放定时器了

//fatherCxt, cancel:= context.WithCancel(rootCxt)
	fatherCxt, cancel:= context.WithTimeout(rootCxt, time.Second * 10)

让程序变得优雅,使用waitGroup

// 收拾桌子
func ClearTable(cxt context.Context, top string, wait *sync.WaitGroup) {
	defer func() {
		wait.Done()
		fmt.Println(top, "跑路,回家玩游戏咯~")
	}()
	index := 0
	for {
		select {
		case <-cxt.Done():
			fmt.Println(top, "老板说叫我滚蛋")
			return
		default :
			if index >= 5{
				return
			}
			index++
			fmt.Println(top, fmt.Sprintf("擦完%v张桌子", index))
			time.Sleep(time.Second * 1)
		}
	}
}

// 摆放椅子
func ClearUpChair(cxt context.Context, top string, wait *sync.WaitGroup) {
	defer func() {
		wait.Done()
		fmt.Println(top, "跑路,回家看电视咯~")
	}()
	index := 0
	for {
		select {
		case <-cxt.Done():
			fmt.Println(top, "老板说叫我滚蛋")
			return
		default :
			if index >= 5{
				return
			}
			index++
			fmt.Println(top, fmt.Sprintf("整理%v张椅子", index))
			time.Sleep(time.Second * 2)
		}
	}
}

// 拖地
func MopTheFloor(cxt context.Context, top string, wait *sync.WaitGroup) {
	defer func() {
		wait.Done()
		fmt.Println(top, "跑路,回家织毛衣咯~")
	}()

	index := 0
	for {
		select {
		case <-cxt.Done():
			fmt.Println(top, "老板说叫我滚蛋")
			return
		default :
			if index >= 1{
				return
			}
			index++
			fmt.Println(top, "开始拖地板")
			time.Sleep(time.Second * 10)
		}
	}
}


func main(){
	defer func() {
		fmt.Println("餐馆忙碌的一天结束。")
	}()
	rootCxt := context.Background()
	//fatherCxt, cancel:= context.WithCancel(rootCxt)
	fatherCxt, _:= context.WithTimeout(rootCxt, time.Second * 9)
	fatherWait := &sync.WaitGroup{}		// 参数传递要是指针,不能值拷贝

	fatherWait.Add(3)
	go ClearTable(fatherCxt, "【员工A】", fatherWait)
	go ClearUpChair(fatherCxt, "【员工B】", fatherWait)
	go MopTheFloor(fatherCxt, "【员工C】", fatherWait)

	fatherWait.Wait()		// 阻塞,等待3个员工做完事
}

解决问题二,让上层不会影响到下游(分上中下游)

// 擦桌子
func WipeTable(cxt context.Context){
	for {
		select {
		case <-cxt.Done():
			fmt.Println("不擦桌子了")
			return
		default :
			time.Sleep(time.Millisecond * 500)
			fmt.Println("擦完桌子")
		}
		break
	}
}

// 丢垃圾
func lose(cxt context.Context){
	for {
		select {
		case <-cxt.Done():
			fmt.Println("不丢垃圾")
			return
		default :
			time.Sleep(time.Millisecond * 500)
			fmt.Println("丢垃圾")
		}
		break
	}
}


// 收拾桌子
func ClearTable(cxt context.Context, top string, wait *sync.WaitGroup) {
	defer func() {
		wait.Done()
		fmt.Println(top, "跑路,回家玩游戏咯~")
	}()
	index := 0

	newCxt := context.TODO()
	sunCxt, _ := context.WithCancel(newCxt)
	for {
		select {
		case <-cxt.Done():
			fmt.Println(top, "老板说叫我滚蛋")
			return
		default :
			if index >= 5{
				return
			}
			index++
			WipeTable(sunCxt)
			lose(sunCxt)
			time.Sleep(time.Millisecond * 500)
			fmt.Println(top, fmt.Sprintf("擦完%v张桌子", index))
			time.Sleep(time.Millisecond * 500)
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值