使用goroutines和频道的GO并发。
#go #并发性

“ GO都可以为兄弟姐妹,DevOps和SRE服务,从其快速构建时间和精益语法到其安全性和可靠性支持。GO的并发和网络功能也使其非常适合管理管理云部署的工具随着开发基础架构的增长,随着时间的推移会增长速度和代码可维护性时,很容易支持自动化。” Development Operations & Site Reliability Engineering

介绍

并发是现代软件开发的重要方面,它允许独立执行多个任务,从而提高应用程序的整体性能和响应能力。在Go中,由Google设计的静态构图的语言,并发是开发人员绝对喜欢的内置功能之一。使之成为可能的关键组件之一是goroutine。

什么是goroutine?

Goroutine是由GO运行时管理的轻量级线程。它允许您与其他功能同时运行功能。 Goroutines是允许GO程序轻松实现并行和并发处理的关键要素之一。与传统线程不同,goroutines可以更便宜,并且它们的堆栈尺寸会动态增长和收缩,使其更有效。

package main

import (
    "fmt"
    "time"
)

func printMessage(msg string) {
    for i := 0; i < 10; i++ {
        fmt.Println(msg)
        time.Sleep(time.Millisecond * 100)
    }
}

func main() {
    go printMessage("Black")
    printMessage("Yellow")
}

输出

Yellow
Black
Black
Yellow
Yellow
Black
Black
Yellow
Yellow
Black
Black
Yellow
Yellow
Black
Black
Yellow
Yellow
Black
Black
Yellow

在此示例中,PrintMessage函数被称为两次:一次为Goroutine,一次是正常函数调用。两者都将同时执行。

使用通道同步

虽然Goroutines使实施并发变得易于实现,但它们也提出了挑战,尤其是在协调任务或共享数据时。 GO提供了一种称为“通道”的机制,用于在Goroutines之间安全通信。

这是一个使用通道同步两个goroutines的示例:

package main

import "fmt"

func printMessage(msg string, ch chan string) {
    fmt.Println(msg)
    ch <- "Done"
}

func main() {
    ch := make(chan string)

    go printMessage("Hello", ch)
    go printMessage("World", ch)

    fmt.Println(<-ch)
    fmt.Println(<-ch)

}

输出

World
Done
Hello
Done

在此示例中,每个Goroutine完成其任务后在Channel CH上发送“完成”消息。主要功能等待在终止之前接收两条“完成”消息。

使用Goroutines的优点

  1. 资源效率: goroutines非常轻巧,只需要几千键的堆栈内存。
  2. 易用性:使用简单的GO关键字,您可以将大多数功能转换为Goroutines。
  3. 内置支持: GO运行时本质上支持Goroutines,消除了对任务调度或上下文切换的第三方库的需求。

Goroutines vs螺纹

  1. 堆栈尺寸:线程通常具有固定的堆栈尺寸,通常在1-2MB左右,而Goroutines则从可以动态生长和收缩的较小堆栈开始。
  2. 创建成本: goroutines与线程相比,在内存和CPU时间方面创建要便宜得多。
  3. 调度: goroutines由GO运行时协作安排,这简化了设计与预先安排的线程相比。

例子:

使用sync.WaitGroup进行同步

您可以使用sync.WaitGroup,而不是使用通道进行同步。 WaitGroup等待一系列goroutines完成执行。

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
    wg.Done()
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
}

使用goroutines并行求和

在此示例中,我们将一个数组分为两半,并同时总和。

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum
}

func main() {
    data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    c := make(chan int)

    go sum(data[:len(data)/2], c)
    go sum(data[len(data)/2:], c)

    x, y := <-c, <-c

    fmt.Printf("Sum1: %d, Sum2: %d, Total Sum: %d\n", x, y, x+y)
}

使用频道选择

SELECT语句允许您等待多个频道操作,类似于Switch的价值类型。

package main

import (
    "fmt"
    "time"
)

func server1(ch chan string) {
    time.Sleep(time.Second * 2)
    ch <- "Response from server 1"
}

func server2(ch chan string) {
    time.Sleep(time.Second * 1)
    ch <- "Response from server 2"
}

func main() {
    output1 := make(chan string)
    output2 := make(chan string)

    go server1(output1)
    go server2(output2)

    select {
    case msg1 := <-output1:
        fmt.Println(msg1)
    case msg2 := <-output2:
        fmt.Println(msg2)
    }
}

在此示例中,两个“服务器”在其各自的频道上发送消息。我们使用选择等待第一个响应并打印它。

计时器和股票

计时器和股票是可以用于调度的其他重要功能。

package main

import (
    "fmt"
    "time"
)

func main() {
    timer := time.NewTimer(time.Second * 2)
    ticker := time.NewTicker(time.Second)

    go func() {
        for t := range ticker.C {
            fmt.Println("Tick at", t)
        }
    }()

    <-timer.C
    ticker.Stop()
    fmt.Println("Timer expired")
}

在此示例中,股票每秒勾选一次,计时器在两秒钟后到期。计时器到期后,股票被停止。

这些示例展示了GO并发模型的不同方面,可帮助您了解多功能和有用的goroutines。

Goroutines是实现并发任务的强大功能。它们易于使用,高效且融合到语言及其标准库中。渠道通过提供一种安全的方式来共享同时运行goroutines之间的数据,从而进一步提高了其可用性。当您深入研究时,您会发现Goroutines和频道是开发工具包中必不可少的工具。