首页 > 其他分享 >Golang并发控制方式有几种?

Golang并发控制方式有几种?

时间:2024-01-27 19:33:42浏览次数:22  
标签:wg goroutine sync 几种 Golang 并发 Context 我们 channel

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: 恋水无意
腾讯云开发者社区:孟斯特


标签:wg,goroutine,sync,几种,Golang,并发,Context,我们,channel
From: https://www.cnblogs.com/lianshuiwuyi/p/17991829

相关文章

  • 实现多线程的方式有哪几种?
    Java虚拟机时是运行所有Java程序的抽象计算机,允许应用并发的运行多个线程。在Java语言中,多线程的实现,一般有以下3中方法:1.实现Runnable接口,并实现该接口的run()方法;主要步骤:1.自定义类并实现Runnable接口,实现run()方法;2.创建Thread类,用实现Runnable接口的对象作为参数实例化......
  • golang 遍历目录的两种方式、删除目录的两种方式
    funcmain(){ directory:="/Users/mike/Downloads" //不会递归只会读取当前的单层目录 directories,err:=os.ReadDir(directory) iferr!=nil{ fmt.Println(err) } for_,d:=rangedirectories{ fmt.Println(d.Name(),d.IsDir()) } //会递归遍历所......
  • openGauss学习笔记-208 openGauss 数据库运维-常见故障定位案例-TPCC高并发长稳运行因
    openGauss学习笔记-208openGauss数据库运维-常见故障定位案例-TPCC高并发长稳运行因脏页刷盘效率导致性能下降208.1TPCC高并发长稳运行因脏页刷盘效率导致性能下降208.1.1问题现象TPCC高并发长稳运行因脏页刷盘效率导致性能下降,具体表现为:初始性能较高,随着运行时间增加,数据......
  • 用Promise解决并发
    前提假设有两个或多个网络请求函数如果使用asyn/await,假设一个请求需要2s,那么总共需要4s如果使用并发,则两秒即可完成两个请求constgetA=>(){returnPromise((resolve,reject)=>{setTimeout(()=>{reject('reject')},2000)})......
  • Windows快捷键重启的几种方式
    Ctrl+Alt+Delete:按下这个组合键后,您可以选择重新启动电脑或其他选项Ctrl+Shift+Esc:直接打开任务管理器,然后在任务管理器中选择“文件”>“运行新任务”,输入"shutdown/r"执行重新启动命令。Win+X,然后选择“关机或注销”菜单中的“重新启动”。请注意,不同的操作系统......
  • Golang 语言入门:基础语法与示例
    引言Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。自2009年推出以来,Go已经成为云计算、微服务、网络服务器等领域的热门选择。其设计哲学是简洁、快速和易于理解,这使得Go语言特别适合当今快速发展的软件行业。Go语言的基本语法......
  • .net 高并发(二,多线程)
    一,多线程可以通过System.Threading.Thread类来实现。下面是一个简单的示例,展示如何使用Thread类创建和管理多个线程: usingSystem; usingSystem.Threading;   classProgram { staticvoidMain() { //创建两个线程 Thre......
  • .net 高并发(一,异步编程模型)
    在.NET中,异步编程模型(Async/Await)是一种处理高并发的好方法。它允许开发人员以非阻塞的方式编写异步代码,从而使应用程序能够同时处理多个请求或任务,从而提高并发性能。下面是使用Async/Await进行异步编程的一般步骤:定义一个返回Task或Task<TResult>的方法,并在方法签名中使用as......
  • go-carbon v2.3.7 发布,轻量级、语义化、对开发者友好的 golang 时间处理库
    carbon是一个轻量级、语义化、对开发者友好的golang时间处理库,支持链式调用。目前已被awesome-go收录,如果您觉得不错,请给个star吧github.com/golang-module/carbongitee.com/golang-module/carbon安装使用Golang版本大于等于1.16//使用github库goget-ugithu......
  • vue中动态添加style样式的几种写法总结
    项目中可能会需要动态添加style行内样式,但是在长期维护的项目里面,尽量要避免使用。注意:1、凡是有-的style属性名都要变成驼峰式,比如font-size要变成fontSize。2、除了绑定值,其他的属性名的值要用引号括起来,比如backgroundColor:'#00a2ff'而不是backgroundColor:#00a2ff。......