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 之间的区别。

相关推荐
淡海水8 小时前
07-04-并发-ConcurrentBag-T-工作窃取WorkStealing算法
开发语言·算法·c#·bag·concurrent·workstealing
CS_Zero8 小时前
C语言sizeof是函数吗?
c语言·开发语言
东风破_8 小时前
从跨域到 WebSocket:前端跨域方案、SSE 与双向实时通信详解
前端·后端
-今昭-9 小时前
《V2V 迁移实战:将 VMware 虚拟机转为 OpenStack 可用 Glance qcow2 镜像》
开发语言·python
行百里er10 小时前
Gateway 上别硬塞 MVC:DeferredImportSelector 看菜下锅
spring boot·后端·开源
名字还没想好☜10 小时前
Java 遍历时删元素抛 ConcurrentModificationException:fail-fast 原理与三种正确删法
后端
IT_陈寒11 小时前
React Hooks闭包陷阱差点让我加班到凌晨
前端·人工智能·后端
黑马程序员毕设11 小时前
基于Java的医院药品管理系统的优化设计与实现
java·开发语言·spring boot·小程序·架构·课程设计·毕设
码事漫谈11 小时前
本体论,彻底懂
后端
_Twink1e11 小时前
openJiuwen 实训营 Day 1 · WorkSwarm 入门教程
开发语言·c++·openjiuwen