Home

Goroutine与Channel

Goroutine是什么

Golang的一大特色就是他原生支持高并发。高并发这件事,即使是慢如Python也是可以应付的,为什么Golang在这方面如此“出彩”呢。

因为他从关键字层面实现了多协程开发。

实现并发的一大因素就是Goroutine,在一个Go程序中,我们如何创建一个Goroutine呢,非常简单


go f()

就这样,我们创建了一个Goroutine,值得注意的是,Goroutine不同于传统意义上的线程或是协程,他兼顾了两者的优点。

Goroutine 的调度原理

Goroutine 的调度由 Go 语言的运行时 (runtime) 负责,这种调度机制通常被称为 M

模型。

具体实现细节

  1. Goroutine:

    • 每个 Goroutine 都有自己的栈空间,初始栈很小(通常是 2KB),并且可以根据需要动态增长。这使得 Goroutine 相比于传统线程更加轻量,不会浪费大量的内存。
  2. P (Processor):

    • Go 运行时引入了“P”的概念,表示逻辑处理器。P 是运行 Goroutine 所需的资源,它管理了 Goroutine 队列。在默认情况下,P 的数量等于可用 CPU 核心数,可以通过环境变量 GOMAXPROCS 设置。

    • 每个 P 都会被绑定到一个操作系统线程上,当线程运行时,P 会从自己的 Goroutine 队列中取出 Goroutine 执行。

  3. M (Machine):

    • M 代表一个操作系统线程,负责执行 P 上的 Goroutine。每个 M 只能绑定一个 P,但 P 可以切换绑定到不同的 M 上。

    • 当 M 运行一个 Goroutine 时,如果 Goroutine 因为系统调用或其他原因阻塞,P 会解除与该 M 的绑定,并寻找一个空闲的 M 来继续运行队列中的其他 Goroutine。

  4. 调度器:

    • Go 的调度器负责管理 G (Goroutine), P (Processor), 和 M (Machine) 之间的关系。当一个 Goroutine 被创建时,它会被放入到 P 的本地队列中。调度器定期检查并决定哪个 Goroutine 应该被运行,以及如何将 P 和 M 进行最优匹配。

    • 当一个 Goroutine 阻塞时,调度器会尝试将其他的 Goroutine 安排到不同的线程上执行,避免 CPU 闲置。

  5. 抢占式调度:

    • Goroutine 的调度是抢占式的,这意味着运行时可以在合适的时间点强制中断 Goroutine 的执行并调度其他的 Goroutine 运行。Go 运行时会定期检查 Goroutine 的运行时间,如果某个 Goroutine 运行过久,就会被强制挂起以便其他 Goroutine 获得执行机会。

Goroutine的好处

在其他语言,我们要实现高并发,那就比如要手动调整线程,协程的运行逻辑,或者引入相关的库,耗时耗力而且效果不一定是最好。Goroutine的调度细节完全由运行时决定,大大降低了调度难度,而且由于是原生支持的,效率还更高

Channel

Go 语言中的通道(channel)是用于在不同的 goroutine 之间进行通信和同步的核心机制。可以理解为一个管道,信息可以从管道的这一头传送到管道的另一头。在Golang里面强调每个Goroutine之间应该采用互相发消息的形式而不是共享内存的形式

通道的实现涉及到复杂的底层数据结构和同步原语。以下是对 Go 语言中通道实现的简要概述:

通道的基本概念

  1. 无缓冲通道:发送操作会阻塞,直到有接收操作为止,反之亦然。

  2. 带缓冲通道:发送操作在缓冲区未满时不会阻塞,接收操作在缓冲区非空时不会阻塞

Example


package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int, 2) // 创建一个带缓冲的通道

    // 启动一个发送者 goroutine
    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
            fmt.Println("Sent:", i)
            time.Sleep(time.Second)
        }
        close(ch) // 关闭通道
    }()

    // 启动一个接收者 goroutine
    go func() {
        for v := range ch {
            fmt.Println("Received:", v)
        }
    }()

    // 等待一段时间,确保所有操作完成
    time.Sleep(10 * time.Second)
}

相关资料

http://interview.wzcu.com/Golang/goroutine.html

https://go.cyub.vip/concurrency/channel/

https://blog.csdn.net/weixin_43870646/article/details/86575142