GoLang 语言高级(1)

核心概念速览:

  • goroutine → 轻量级并发执行单元
  • channel → goroutine 之间传递数据
  • select → 监听多个 channel
  • WaitGroup → 等待一组 goroutine 完成
  • Mutex → 保护共享数据

goroutine 轻量级并发执行单元

goroutine 是 GoLang 提供的一种用户级线程(协程)。Go Runtime 将用户编写的大量 goroutine 调度到少量的操作系统级别的线程上执行。

go 复制代码
goroutine (G) -> Go Runtime 调度 -> OS Thread(线程) -> CPU

这就是 Go 最著名的 GMP 模型。

go 复制代码
package main

import "fmt"

func test() {
    fmt.Println("hello")
}

func main() {
    // 普通的函数调用前加上 go 关键字,该函数就会被当作一个 goroutine 执行
    go test()
}

不过以上的这个模型可能不会有任何输出。因为 main 函数退出,所有的 goroutine 都得退出,不管你执行到了哪里。

可以通过 time.Sleep() 方法,阻塞主进程,让 goroutine 能够有足够的时间执行完毕。

go 复制代码
package main

import (
    "fmt"
    "time"
)

func hello() {
    fmt.Println("hello")
}

func main() {
    go hello()

    time.Sleep(time.Second)
}

WaitGroup

在上面使用了 time.Sleep() 方法来等待 goroutine 执行完毕,仅做演示用。在正式项目中正规的做法是使用 WaitGroup 机制等待 goroutine 执行完毕。

go 复制代码
package main

import (
    "fmt"
    "sync"
)

func hello(wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Println("hello")
}

func main() {
    var wg sync.WaitGroup

    wg.add(1)
    go hello(&wg)

    // 等待 hello 执行完毕
    wg.wait()
}

启动多个 goroutine 并等待它们全部执行完毕。

go 复制代码
package main

import (
    "fmt"
    "sync"
)

func worker(i int, wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Println("worker: " + i)
}

func main() {
    var wg sync.WaitGroup

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

通过匿名函数传递参数,容易出现闭包陷阱的问题。

(闭包捕获循环变量,很多编程语言都有的 bug, Go 1.22 修复了这个问题)

go 复制代码
package main

import "fmt"

func main() {

    // 正确方式
    for i := 0; i < 5; i++ {
        go func(i int) {
            fmt.Println(i)
        }(i)
    }

    // 不推荐的方式
    for j := 0; j < 5; j++ {
        go func() {
            fmt.Println(j)
        }()
    }
}

channel

channel 是一个带有同步能力的队列。

go 复制代码
goroutine A -> channel -> goroutine B

例如:

go 复制代码
package main

func main() {

    ch := make(chan int)

    go func() {
        ch <- 42
    }()

    fmt.Println(<-ch)
}

这个例子体现了 channel 天生就是自带同步功能的。这其中发生了两件事情,数据传递和同步。

数据传递:

go 复制代码
42 -> channel -> main

同步:

go 复制代码
<-ch 没有数据时,会阻塞 main
直到 goroutine ch <- 42,main 接收到了 goroutine 发送过来的 数据,才继续向下执行

带缓冲 channel

通过以下语法:

go 复制代码
ch := make(chan int, 3)

可以创建一个带缓冲的 channel

发送方可以连续发送:

go 复制代码
ch <- 1
ch <- 2
ch <- 3

不会阻塞。

发送了第四个

go 复制代码
ch <- 4 时,发送方才会阻塞

所以带缓冲的 channel 可以当作异步通信,而没带缓冲的 channel 可以被当作同步通信。

这是这两类 channel 之间的区别。

相关推荐
掘金者阿豪2 小时前
PDO连金仓数据库,我把踩过的坑整理了一下(上篇)
后端
zzz_23682 小时前
【Java基础】泛型的门道:伪泛型的真相
java·开发语言
用户34232323763172 小时前
大规模采集架构——从单台网关到千点集群
后端
小鱼仙官2 小时前
Windows Qt调用Vs库实现UDP双口接收数据
开发语言·qt
我登哥MVP2 小时前
SpringCloud 核心组件解析:服务链路追踪
java·spring boot·后端·spring·spring cloud·java-ee·maven
晓杰在写后端2 小时前
从0到1实现Balatro游戏后端(7):Boss Blind与特殊规则实现
后端·游戏开发
iiiiyu2 小时前
IO流相关编程题
java·大数据·开发语言·数据结构·数据库·mysql
张忠琳2 小时前
【Go 1.26.4】(Part 8) Go 1.26.4 超深度分析 — context + reflect + errors
开发语言·golang
这个DBA有点耶2 小时前
核心系统的高可用与容灾架构:从主从到两地三中心全面解析
java·开发语言·数据库·sql·mysql·架构·运维开发