【Go语言基础语法】Goroutine

Goroutine

  Goroutine 是 Go 语言中的一个重要概念,它是一种轻量级的线程,可以在不创建新的操作系统线程的情况下,实现并发和并行编程。

  每个 Goroutine 都是一个独立的执行线程,它们共享相同的内存地址空间,并通过 Go 语言内置的协程(coroutine)机制来实现协作和切换。当一个 Goroutine 执行阻塞操作时(例如 I/O 操作),Go 运行时会自动将其挂起,并切换到其他活跃的 Goroutine 执行。当阻塞操作完成时,被挂起的 Goroutine 会被重新激活,继续执行。

  以下我们通过一些代码来展示 Goroutine 的奇妙之处。

go

  首先,给出如下一份原始代码。main 函数里有 foo 和 bar 两个函数,都实现了输出 0 到 14 的功能。

package main

import "fmt"

func main() {
    foo()
    bar()
}

func foo() {
    for i := 0; i < 15; i++ {
        fmt.Println("Foo:", i)
    }
}

func bar() {
    for i := 0; i < 15; i++ {
        fmt.Println("Bar:", i)
    }
}

  运行代码,结果如下。

Foo: 0
Foo: 1
Foo: 2
Foo: 3
Foo: 4
……………………
Bar: 10
Bar: 11
Bar: 12
Bar: 13
Bar: 14

  Goroutine 的使用呢,很简单,我们在 main函数里的 foo 和 bar 函数前添加 go 关键字即可,这表示我们定义了两个独立的 Goroutine。

func main() {
    go foo()
    go bar()
}

  然而,当我们尝试运行这份修改后的代码时,它却没有任何输出,这是为什么呢?

  这是因为在这个例子中,主 Goroutine(也就是 main 函数) 只是启动了两个子 Goroutine(foo 和 bar),然后立即退出了,没有任何代码来等待子 Goroutine 完成,子 Goroutine 也没有与主 Goroutine 进行任何交互,所以程序会立即结束,没有任何输出。

  因此,我们决定尝试用 sync 包下的 sync.WaitGroup 来解决这个问题。

sync.WaitGroup

  sync.WaitGroup 是 Go 语言中用来等待一组 Goroutine 完成执行的工具,它可以帮助多个 Goroutine 协调工作。sync.WaitGroup 具体来说是一个计数器,通过 Add 方法,它可以记录有多少个 Goroutine 正在等待。当一个 Goroutine 完成了它的工作后,它可以调用 Done 方法来将计数器减 1。同时,主 Goroutine 可以通过调用 Wait 方法来等待所有的 Goroutine 完成,并在所有 Goroutine 完成后继续执行。

  我们利用这个新工具,将代码修改如下。

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {
    wg.Add(2)
    go foo()
    go bar()
    wg.Wait()
}

func foo() {
    for i := 0; i < 15; i++ {
        fmt.Println("Foo:", i)
    }
    wg.Done()
}

func bar() {
    for i := 0; i < 15; i++ {
        fmt.Println("bar:", i)
    }
    wg.Done()
}

  目前,我们虽然解决了程序没有输出的问题,但是同样的一份代码,多次运行的结果却各不相同,这又是为什么呢?

  这是因为 Go 语言中的并发是异步的,并且不保证代码的执行顺序。在这份代码中,两个 Goroutine(foo 和 bar)都会循环打印出这 15 个数字。但由于 Goroutine 是异步执行的,所以它们可能会同时执行,也可能会交替执行。另外需要我们注意的一点,由于 Go 语言中的并发是基于抢占式调度器实现的,所以 Goroutine 的执行可能会被其他 Goroutine 抢占,导致它们的执行顺序也可能会有所不同。因此,程序在每一次运行后,最终的输出结果可能会有所不同。

  既然如此,为了确保代码的执行结果的一致性,我们尝试使用另外一个同步工具 sync.Mutex。

sync.Mutex

  sync.Mutex 是 Go 语言中的一种互斥锁,它可以用于保护共享资源的并发访问。互斥锁是一种同步机制,它确保在同一时刻只有一个 Goroutine 可以访问共享资源。如果一个 Goroutine 想要访问共享资源,它必须先获取互斥锁,然后在访问完资源后释放互斥锁。其他 Goroutine 必须等待,直到互斥锁被释放,才能访问共享资源。

  利用sync.Mutex,我们将代码修改如下。

package main

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

var wg sync.WaitGroup
var mux sync.Mutex

func main() {
    wg.Add(2)
    go foo()
    go bar()
    wg.Wait()
}

func foo() {
    mux.Lock()
    for i := 0; i < 15; i++ {
        fmt.Println("Foo:", i)
    }
    mux.Unlock()
    wg.Done()
}

func bar() {
    time.Sleep(3 * time.Millisecond)
    for i := 0; i < 15; i++ {
        fmt.Println("Bar:", i)
    }
    wg.Done()
}

  注意到代码中,我们还在 bar 函数中添加了一句话 time.Sleep(3 * time.Millisecond) ,这是为了保证先执行 foo 这个子 Goroutine,做到让程序每一次运行的结果真正一致。

  Go 语言中的 time.Sleep 函数用于让当前的 Goroutine 暂停指定的时间。在上面的代码中,time.Sleep(3 * time.Millisecond) 表示让当前的 Goroutine 暂停 3 毫秒,然后再继续执行后面的代码。需要注意的是,time.Sleep 函数并不是一个阻塞函数,它只是让当前的 Goroutine 暂停指定的时间,而不会阻塞其他的 Goroutine。

  分析这份代码,我们通过 time.Sleep 函数,使得一开始资源即使被 bar 这个 Goroutine 抢占,也会先“睡”上三毫秒,把辛苦抢来的资源拱手让给 foo。而在 foo 函数里,我们通过设置 mux 这个互斥锁,使得 foo 在执行的时候“独享”这份资源。即便三毫秒过后,bar 醒来了,但它也会因为没有拿到 mux 这个互斥锁而必须等待。如此一来,便保证了程序每次运行的结果做到真正一致。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值