Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Golang并发控制

详解Golang并发控制的三种方案

作者:拔剑纵狂歌

本文主要介绍了详解Golang并发控制的三种方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

Channel

Channel是Go在语言层面提供的一种协程间的通信方式,我们可以通过在协程中向管道写入数据和在待等待的协程中读取对应协程的次数来实现并发控制。

func main() {
    intChan := make(chan int, 5)
    waitCount := 5
    for i := 0; i < waitCount; i++ {
       go func() {
          intChan <- 1
       }()
    }

    for i := 0; i < waitCount; i++ {
       <-intChan

    }
    fmt.Println("主进程结束")
}

WaitGroup

waitgroup通常应用于等待一组“工作协程”结束的场景,waitgroup底层是由一个长度为3的数组实现的,其内部有两个计数器,一个是工作协程计数器、一个是坐等协程计数器,还有一个是信号量。工作协程全部运行结束后,工作协程计数器将置为0,会释放对应坐等协程次数的信号量。

两点注意:

func main() {
    wg := sync.WaitGroup{}
    wg.Add(2)
    go func() {
       time.Sleep(3 * time.Second)
       fmt.Println("等待三分钟的协程结束了")
       wg.Done()
    }()

    go func() {
       time.Sleep(3 * time.Second)
       fmt.Println("等待三分钟的协程结束了")
       wg.Done()
    }()

    wg.Wait()
}

Context

适用于一个协程派生出多个协程的情况,可以控制多级的goroutine。我们可以通过一个Context对象,对派生出来的树状goroutine进行统一管理,并且每个goroutine具有相同的上下文。做统一关闭操作、统一定时关闭、统一传值的操作。多个上下文协程之间可以互相嵌套配合。

golang实现了四种原生的上下文对象

以上除emptyCtx外的上下文对象和获取实例的方法如下图所示:

 Context示例代码

cancelCtx

我们在所有的派生协程中传入相同的cancelContext对象,并在每一个子协程中使用switch-case结构监听上下文对象是否关闭,如果上下文对象关闭了,ctx.Done()返回的管道就可以读取到一个元素,使所在的case语句可执行,之后退出switch结构,执行协程中的其他代码。

func main() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	deadline, ok := ctx.Deadline()
	fmt.Println(deadline, ok)
	done := ctx.Done()
	fmt.Println(reflect.TypeOf(done))
	fmt.Println(done)

	go HandelRequest(ctx)
	//<-done 阻塞当前一层的goroutine
	time.Sleep(5 * time.Second)
	fmt.Println("all goroutines is stopping!")
	cancelFunc()
	err := ctx.Err()
	fmt.Println(err) //context canceled
	time.Sleep(5 * time.Second)
}

func HandelRequest(ctx context.Context) {
	go WriteMysql(ctx)
	go WriteRedis(ctx)
	for {
		select {
		case <-ctx.Done():
			fmt.Println("HandelRequest Done")
			return
		default:
			fmt.Println("等一等,Handler正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteRedis(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteRedis Done.")
			return
		default:
			fmt.Println("等一等,Redis正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteMysql(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteMysql Done.")
			return
		default:
			fmt.Println("等一等,Mysql正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

timerCtx

这里代码以WithTimeout举例,相比与我们之前的手动调用关闭,使用timerCtx定时上下文对象后,可以是实现到达指定的时间自动进行关闭的操作。

func main() {
	deadline, _ := context.WithTimeout(context.Background(), 5*time.Second)
	go HandelRequest(deadline)

	time.Sleep(10 * time.Second)

}

func HandelRequest(ctx context.Context) {
	go WriteMysql(ctx)
	go WriteRedis(ctx)
	for {
		select {
		case <-ctx.Done():
			fmt.Println("HandelRequest Done")
			return
		default:
			fmt.Println("等一等,Handler正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteRedis(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteRedis Done.")
			return
		default:
			fmt.Println("等一等,Redis正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteMysql(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteMysql Done.")
			return
		default:
			fmt.Println("等一等,Mysql正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

valueCtx

我们可以通过嵌套WithValue上下文,来进行多个key-value在派生协程中传递,共享常量

func main() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	value1 := context.WithValue(ctx, "param1", 1)
	value2 := context.WithValue(value1, "param2", 2)
	go ReadContextValue(value2)

	time.Sleep(10 * time.Second)
	cancelFunc()
	time.Sleep(5 * time.Second)
}

func ReadContextValue(ctx context.Context) {
	fmt.Println(ctx.Value("param1"))
	fmt.Println(ctx.Value("param2"))
}

到此这篇关于详解Golang并发控制的三种方案的文章就介绍到这了,更多相关Golang并发控制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文