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)
}
}
}