多个 Goroutines

多个 Goroutines

Goroutines 是 Go 语言中轻量级的并发机制,它允许我们同时执行多个任务,而且相比于传统的并发技术,它的实现和开销都更为高效。

在这篇文章中,我们将会讨论如何启动和管理多个 Goroutines,以及如何确保它们之间的安全协作。

启动 Goroutines

在 Go 语言中启动一个 Goroutine 非常简单,只需要在函数调用前面添加关键字 go 即可,比如:

func main() {
    go sayHello()
    fmt.Println("Hello from main")
}

func sayHello() {
    fmt.Println("Hello from Goroutine")
}

在上面的例子中,我们启动了一个 Goroutine 来执行 sayHello() 函数,并在同时打印了 "Hello from main"。由于 sayHello() 执行在一个单独的 Goroutine 内,它的执行和 main 函数的打印操作是异步的,因此我们将会看到两行不同的输出。

需要注意的是,Goroutine 的执行并不受主函数的约束,如果主函数运行结束后 Goroutine 仍未完成,那么它会随着主函数的关闭而被终止。

传递参数

我们可以通过在 Goroutine 的函数调用后面添加参数的方式,把一些值传递给 Goroutine 内部的函数,比如:

func main() {
    go sayHello("Bob")
    fmt.Println("Hello from main")
}

func sayHello(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

在上面的例子中,我们在 Goroutine 的函数调用后面传递了一个参数 "Bob",并在 Goroutine 内部的打印语句中使用了这个参数。由于 Goroutine 执行是异步的,所以我们不能保证 "Hello, Bob!" 的打印顺序和 "Hello from main" 的顺序一定一致,但是它们都将在 main 函数返回前完成执行。

同步操作

由于 Goroutines 的执行是异步的,当我们处于多线程状态时,我们需要考虑如何保证它们之间的协作并发的安全。这是非常关键的一个问题。

在 Go 语言中,我们可以使用 mutex、channel 及 WaitGroup 来达到这一目的。

Mutex

Mutex(互斥锁)是同步 Goroutine 的一种简单方式,它能够锁住任意资源,然后使其他 Goroutine 等待这个锁被释放之后才能够访问这个资源。

在下面的示例中,我们创建了一个互斥锁,并使用 Lock 和 Unlock 方法来访问受锁保护的变量。使用互斥锁保护的变量可以确保单一 Goroutine 在同一时刻访问它,避免了数据竞争。

var counter int = 0
var mutex sync.Mutex

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go incrementCounter(&wg)
    }
    wg.Wait()
    fmt.Println("Counter:", counter)
}

func incrementCounter(wg *sync.WaitGroup) {
    mutex.Lock()
    counter++
    mutex.Unlock()
    wg.Done()
}

在上面的代码中,我们创建了一个互斥锁,并使用 Lock 和 Unlock 方法保护了 counter 这个变量。在 incrementCounter 函数内部,我们使用了 Lock 方法来确保单一 Goroutine 在访问 counter 变量时候被锁住,接着我们对 counter 变量进行了递增操作,最后使用了 Unlock 方法来释放锁。

Channel

Channel 是 Goroutine 之间进行通信的主要方式。它是一种类型安全的、阻塞的消息传递机制,可以用来在不同的 Goroutine 之间交换信息。

在下面的示例中,我们创建了一个 channel,然后在两个 Goroutine 之间传递消息,其中第一个 Goroutine 向 channel 内部写入了一个消息,第二个 Goroutine 从该 channel 中读取这个消息并进行了打印。

func main() {
    message := make(chan string)
    go func() {
        message <- "Hello from Goroutine"
    }()
    fmt.Println(<-message)
}

在上面的代码中,我们创建了一个 channel 并将其赋值给 message 变量,接着我们用一个内联函数开启了一个 Goroutine,向这个 channel 内部写入了一条信息 "Hello from Goroutine",最后在 main 函数内部使用 <- 运算符从 message channel 中读取这个消息并打印。

需要注意的是,当我们使用 <- 运算符从 channel 中读取消息时,这个运算符将阻塞当前 Goroutine 直到 channel 中有数据可以读取为止,因此我们可以将 channel 看做是一种阻塞机制。

WaitGroup

WaitGroup 可以让我们等待一系列 Goroutine 的执行完成,而不用事先知道这些 Goroutine 执行的次数。

在下面的代码中,我们首先使用 for 循环生成了 5 个 Goroutine 并将其启动。然后我们再开启一个额外的 Goroutine 来等待这 5 个 Goroutine 的执行完成,并在执行完成后进行打印。

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go process(&wg)
    }
    wg.Wait()
    fmt.Println("All Goroutine done!")
}

func process(wg *sync.WaitGroup) {
    time.Sleep(time.Second)
    fmt.Println("Goroutine done!")
    wg.Done()
}

在上面的代码中,我们首先定义了一个 WaitGroup 变量 wg,然后使用 for 循环生成了 5 个 Goroutine 并开启对应的 process 函数。在 process 函数内部,我们使用 time 包中定义的 time.Sleep(time.Second) 函数模拟了一段耗时的操作,随后我们使用 wg.Done() 方法标记了当前 Goroutine 的执行结束,并等待其他 Goroutine 执行完成。在 main 函数内部,我们使用 wg.Wait() 方法来等待所有 Goroutine 的执行完成,最后在控制台内打印出所有 Goroutine 执行结束的标志语。

结论

在这篇文章中,我们详细介绍了如何启动和管理多个 Goroutine,包括使用 Mutex、Channel 和 WaitGroup 等多种用于同步 Goroutine 的技术。在 Go 语言的多线程编程中,了解如何正确地启动、停止和同步 Goroutine 的执行是至关重要的,也是实现高效、安全并发程序必备的基本技能之一。

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程