Go语言中的goroutine是一种轻量级的线程,其优点在于占用资源少、切换成本低,能够高效地实现并发操作。但如何对这些并发的goroutine进行控制呢?

一提到并发控制,大家最先想到到的是锁。Go中同样提供了锁的相关机制,包括互斥锁sync.Mutex和读写锁sync.RWMutex;除此之外Go还提供了原子操作sync/atomic。但这些操作都是针对并发过程中的数据安全的,并不是针对goroutine本身的。

本文主要介绍的是对goroutine并发行为的控制。在Go中最常见的有三种方式:sync.WaitGroupchannelContext

1. sync.WaitGroup

sync.WaitGroup是Go语言中一个非常有用的同步原语,它可以帮助我们等待一组goroutine全部完成。在以下场景中,我们通常会使用sync.WaitGroup:

  • 当我们需要在主函数中等待一组goroutine全部完成后再退出程序时。
  • 当我们需要在一个函数中启动多个goroutine,并确保它们全部完成后再返回结果时。
  • 当我们需要在一个函数中启动多个goroutine,并确保它们全部完成后再执行某个操作时。
  • 当我们需要在一个函数中启动多个goroutine,并确保它们全部完成后再关闭某个资源时。
  • 当我们需要在一个函数中启动多个goroutine,并确保它们全部完成后再退出循环时。

在使用sync.WaitGroup时,我们需要先创建一个sync.WaitGroup对象,然后使用它的Add方法来指定需要等待的goroutine数量。接着,我们可以使用go关键字来启动多个goroutine,并在每个goroutine中使用sync.WaitGroup对象的Done方法来表示该goroutine已经完成。最后,我们可以使用sync.WaitGroup对象的Wait方法来等待所有的goroutine全部完成。

下面是一个简单的示例,会启动3个goroutine,分别休眠0s、1s和2s,主函数会在这3个goroutine结束后退出:

package main

import (
"fmt"
"sync"
"time"
) func main() {
var wg sync.WaitGroup for i := 0; i < 3; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("sub goroutine sleep: %ds\n", i)
time.Sleep(time.Duration(i) * time.Second)
}(i)
} wg.Wait()
fmt.Println("main func done")
}

2. channel

在Go语言中,使用channel可以帮助我们更好地控制goroutine的并发。以下是一些常见的使用channel来控制goroutine并发的方法:

2.1 使用无缓冲channel进行同步

我们可以使用一个无缓冲的channel来实现生产者-消费者模式,其中一个goroutine负责生产数据,另一个goroutine负责消费数据。当生产者goroutine将数据发送到channel时,消费者goroutine会阻塞等待数据的到来。这样,我们可以确保生产者和消费者之间的数据同步。

下面是一个简单的示例代码:

package main

import (
"fmt"
"sync"
"time"
) func producer(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("produced", i)
time.Sleep(100 * time.Millisecond)
}
close(ch)
} func consumer(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
for i := range ch {
fmt.Println("consumed", i)
time.Sleep(150 * time.Millisecond)
}
} func main() {
var wg sync.WaitGroup
ch := make(chan int) wg.Add(2)
go producer(ch, &wg)
go consumer(ch, &wg) wg.Wait()
}

在这个示例中,我们创建了一个无缓冲的channel,用于在生产者goroutine和消费者goroutine之间传递数据。生产者goroutine将数据发送到channel中,消费者goroutine从channel中接收数据。在生产者goroutine中,我们使用time.Sleep函数来模拟生产数据的时间,在消费者goroutine中,我们使用time.Sleep函数来模拟消费数据的时间。最后,我们使用sync.WaitGroup来等待所有的goroutine全部完成。

2.2 使用有缓冲channel进行限流

我们可以使用一个有缓冲的channel来限制并发goroutine的数量。在这种情况下,我们可以将channel的容量设置为我们希望的最大并发goroutine数量。然后,在启动每个goroutine之前,我们将一个值发送到channel中。在goroutine完成后,我们从channel中接收一个值。这样,我们可以保证同时运行的goroutine数量不超过我们指定的最大并发数量。

下面是一个简单的示例代码:

package main

import (
"fmt"
"sync"
) func main() {
var wg sync.WaitGroup
maxConcurrency := 3
semaphore := make(chan struct{}, maxConcurrency) for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
semaphore <- struct{}{}
fmt.Println("goroutine", i, "started")
// do some work
fmt.Println("goroutine", i, "finished")
<-semaphore
}()
} wg.Wait()
}

在这个示例中,我们创建了一个带缓冲的channel,缓冲区大小为3。然后,我们启动了10个goroutine,在每个goroutine中,我们将一个空结构体发送到channel中,表示该goroutine已经开始执行。在goroutine完成后,我们从channel中接收一个空结构体,表示该goroutine已经完成执行。这样,我们可以保证同时运行的goroutine数量不超过3。

3. Context

在Go语言中,使用Context可以帮助我们更好地控制goroutine的并发。以下是一些常见的使用Context来控制goroutine并发的方法:

3.1 超时控制

在某些情况下,我们需要对goroutine的执行时间进行限制,以避免程序长时间阻塞或者出现死锁等问题。使用Context可以帮助我们更好地控制goroutine的执行时间。我们可以创建一个带有超时时间的Context,然后将其传递给goroutine。如果goroutine在超时时间内没有完成执行,我们可以使用Context的Done方法来取消goroutine的执行。

下面是一个简单的示例代码:

package main

import (
"context"
"fmt"
"time"
) func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() go func() {
for {
select {
case <-ctx.Done():
fmt.Println("goroutine finished")
return
default:
fmt.Println("goroutine running")
time.Sleep(500 * time.Millisecond)
}
}
}() time.Sleep(3 * time.Second)
}

在这个示例中,我们创建了一个带有超时时间的Context,然后将其传递给goroutine。在goroutine中,我们使用select语句来监听Context的Done方法,如果Context超时,我们将会取消goroutine的执行。

3.2 取消操作

在某些情况下,我们需要在程序运行过程中取消某些goroutine的执行。使用Context可以帮助我们更好地控制goroutine的取消操作。我们可以创建一个带有取消功能的Context,然后将其传递给goroutine。如果需要取消goroutine的执行,我们可以使用Context的Cancel方法来取消goroutine的执行。

下面是一个简单的示例代码:

package main

import (
"context"
"fmt"
"sync"
"time"
) func main() {
ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("goroutine finished")
return
default:
fmt.Println("goroutine running")
time.Sleep(500 * time.Millisecond)
}
}
}() time.Sleep(2 * time.Second)
cancel()
wg.Wait()
}

在这个示例中,我们创建了一个带有取消功能的Context,然后将其传递给goroutine。在goroutine中,我们使用select语句来监听Context的Done方法,如果Context被取消,我们将会取消goroutine的执行。在主函数中,我们使用time.Sleep函数来模拟程序运行过程中的某个时刻需要取消goroutine的执行,然后调用Context的Cancel方法来取消goroutine的执行。

3.3 资源管理

在某些情况下,我们需要对goroutine使用的资源进行管理,以避免资源泄露或者出现竞争条件等问题。使用Context可以帮助我们更好地管理goroutine使用的资源。我们可以将资源与Context关联起来,然后将Context传递给goroutine。当goroutine完成执行后,我们可以使用Context来释放资源或者进行其他的资源管理操作。

下面是一个简单的示例代码:

package main

import (
"context"
"fmt"
"sync"
"time"
) func worker(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("goroutine finished")
return
default:
fmt.Println("goroutine running")
time.Sleep(500 * time.Millisecond)
}
}
} func main() {
ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup
wg.Add(1)
go worker(ctx, &wg) time.Sleep(2 * time.Second)
cancel()
wg.Wait()
}

在这个示例中,我们创建了一个带有取消功能的Context,然后将其传递给goroutine。在goroutine中,我们使用select语句来监听Context的Done方法,如果Context被取消,我们将会取消goroutine的执行。在主函数中,我们使用time.Sleep函数来模拟程序运行过程中的某个时刻需要取消goroutine的执行,然后调用Context的Cancel方法来取消goroutine的执行。


声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。

Author: mengbin

blog: mengbin

Github: mengbin92

cnblogs: 恋水无意

腾讯云开发者社区:孟斯特


Golang并发控制方式有几种?的更多相关文章

  1. Golang中map的三种声明方式和简单实现增删改查

    package main import ( "fmt" ) func main() { test3 := map[string]string{ "one": & ...

  2. Golang 序列化方式及对比

    Golang 序列化方式及对比 - fengfengdiandia的专栏 - CSDN博客 https://blog.csdn.net/fengfengdiandia/article/details/ ...

  3. Golang解析json的几种方法

    Golang解析json的几种方法 概要 使用Golang调用其它平台API接口时总会被多层的json串给恶心到,我记录一下自己解析json的几种方法. 一.自带的json包 func JsonUnm ...

  4. Android启动Activity的两种方式与四种启动模式

    1.在一个Activity中调用startActivity()方法 2.在一个Activity中调用startActivityRequest()方法. 重写onActivityResult方法,用来接 ...

  5. curl文件上传有两种方式,一种是post_fileds,一种是infile

    curl文件上传有两种方式,一种是POSTFIELDS,一种是INFILE,POSTFIELDS传递@实际地址,INFILE传递文件流句柄! );curl_setopt($ch, CURLOPT_PO ...

  6. Golang丰富的I/O----用N种Hello World展示

    h1 { margin-top: 0.6cm; margin-bottom: 0.58cm; direction: ltr; color: #000000; line-height: 200%; te ...

  7. 一分钟上手, 让 Golang 操作数据库成为一种享受

    gorose, 最风骚的 go orm, 拥有链式操作, 开箱即用, 一分钟上手等八大风骚, 让 golang 操作数据库成为一种享受, 妈妈再也看不到我处理数据的痛苦了, 下面就来为大家一一讲解 g ...

  8. 在一个web 应用中,改变url无非是2种方式,一种是利用超链接进行跳转,另一种是使用浏览器的前进和回退功能

    在一个web 应用中,改变url无非是2种方式,一种是利用超链接进行跳转,另一种是使用浏览器的前进和回退功能 https://www.jianshu.com/p/27ee7df4ccc1

  9. 在JVM中,新生代和旧生代有何区别?GC的回收方式有几种?server和client有和区别?

    在JVM中,新生代和旧生代有何区别?GC的回收方式有几种?server和client有和区别? 2014-04-12 12:09 7226人阅读 评论(0) 收藏 举报  分类: J2SE(5)  一 ...

  10. 项目一:第十二天 1、常见权限控制方式 2、基于shiro提供url拦截方式验证权限 3、在realm中授权 5、总结验证权限方式(四种) 6、用户注销7、基于treegrid实现菜单展示

    1 课程计划 1. 常见权限控制方式 2. 基于shiro提供url拦截方式验证权限 3. 在realm中授权 4. 基于shiro提供注解方式验证权限 5. 总结验证权限方式(四种) 6. 用户注销 ...

随机推荐

  1. Flutter加固原理及加密处理

    ​ 引言 为了保护Flutter应用免受潜在的漏洞和攻击威胁,加固是必不可少的措施之一.Flutter加固原理主要包括代码混淆.数据加密.安全存储.反调试与反分析.动态加载和安全通信等多个方面.通过综 ...

  2. Log4Shell 漏洞披露已近一年,它对我们还有影响吗?

    在 Log4Shell 高危漏洞事件披露几乎整整一年之后,新的数据显示,对全球大多数组织来说,补救工作是一个漫长.缓慢.痛苦的过程. 根据漏洞扫描领先者 Tenable 公司的遥测数据来看,截至今年1 ...

  3. 火山引擎DataTester:AB测试技术揭秘及应用分享

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群   在全球软件工程创新峰会上,火山引擎DataTester 研发负责人韩云飞围绕"AB测试:让数据与业务 ...

  4. 火山引擎DataLeap的Catalog系统搜索实践 (二):整体架构

    整体架构 火山引擎DataLeap的Catalog搜索系统使用了开源的搜索引擎Elasticsearch进行基础的文档检索(Recall阶段),因此各种资产元数据会被存放到Elasticsearch中 ...

  5. django 生成二维码

    def generate_qrcode(request): data = 'hello' img = qrcode.make(data) buf = io.BytesIO() img.save(buf ...

  6. 用ChatGPT 玩转哔哩哔哩

    用ChatGPT 玩转哔哩哔哩 哔哔终结者 BibiGPT 哔哩哔哩 BiliGPT,一款智能AI工具,帮助用户一键总结 哔哩哔哩视频内容,非常省心,软件基于GPT-3.5 AI,如果无法使用,可以使 ...

  7. 极限五分钟,在宝塔中用 Docker 部署升讯威在线客服系统

    我在业余时间开发维护了一款免费开源的升讯威在线客服系统,也收获了许多用户.对我来说,只要能获得用户的认可,就是我最大的动力. 最近客服系统成功经受住了客户现场组织的压力测试,获得了客户的认可. 客户组 ...

  8. JavaScript WebSocket C# SuperSocket.WebSocket 示例

    C# WebSocket 后端代码: using Newtonsoft.Json; using SuperSocket.WebSocket; using System; using System.Co ...

  9. 电缆厂 3D 可视化管控系统 | 图扑数字孪生

    近年来,我国各类器材制造业已经开始向数字化生产转型,使得生产流程变得更加精准高效.通过应用智能设备.物联网和大数据分析等技术,企业可以更好地监控生产线上的运行和质量情况,及时发现和解决问题,从而提高生 ...

  10. kafka集群六、java操作kafka(没有密码验证)

    系列导航 一.kafka搭建-单机版 二.kafka搭建-集群搭建 三.kafka集群增加密码验证 四.kafka集群权限增加ACL 五.kafka集群__consumer_offsets副本数修改 ...