当前位置: 首页>编程语言>正文

编程笔记 Golang基础 035 Goroutines

编程笔记 Golang基础 035 Goroutines

  • 一、基本概念
  • 二、应用示例
  • 三、高度模型

一、基本概念

Goroutines 是 Go 语言中用于实现并发编程的关键特性之一,它是轻量级的线程或协程。Go 运行时可以高效地创建和管理数以千计的 Goroutines,并通过多核处理器进行并行执行,极大地提高了程序性能和资源利用率。

  • Goroutine 创建:在 Go 中,启动一个 Goroutine 非常简单,只需在函数调用前加上 go 关键字即可异步执行该函数。例如:

    go funcName(param1, param2)
    

    或者直接定义匿名函数并启动:

    go func() {
        // 这里是 Goroutine 代码块
    }()
    
  • 调度机制:Go 的运行时会自动调度这些 Goroutines 在多个操作系统的线程(M:N 调度模型)上执行,使得它们看起来像是同时运行的。

  • 资源消耗:相比于操作系统级别的线程,Goroutines 的创建、销毁和上下文切换成本更低,因为这些都是由 Go 运行时内部优化完成的。

  • 通信与同步:虽然 Goroutines 可以独立运行,但它们之间经常需要通信和同步。Go 提供了 Channels(通道)这一原生支持来安全地在 Goroutines 间传递数据和同步执行流程。

二、应用示例

下面是一个简单的 Goroutine 应用示例,展示了如何使用 Goroutines 和 Channel 进行并发计算:

package main

import (
	"fmt"
)

// 定义一个计算任务的函数
func worker(id int, jobs <-chan int, results chan<- int) {
	for j := range jobs {
		fmt.Printf("Worker %d started job %d\n", id, j)
		// 模拟耗时工作
		result := j * j
		results <- result
		fmt.Printf("Worker %d finished job %d with result %d\n", id, j, result)
	}
}

func main() {
	// 创建两个 Channel,分别用于传递任务和接收结果
	jobs := make(chan int, 100)
	results := make(chan int, 100)

	// 启动两个 Goroutine 来处理任务
	go worker(1, jobs, results)
	go worker(2, jobs, results)

	// 发送一些任务到 Channel
	for w := 1; w <= 5; w++ {
		jobs <- w
	}
	close(jobs) // 发送完所有任务后关闭 jobs Channel

	// 收取并打印结果
	for a := 1; a <= 5; a++ {
		fmt.Println(<-results)
	}
}

在这个例子中,我们创建了两个 Goroutines 执行名为 worker 的函数,该函数从 jobs Channel 接收任务并把计算结果发送到 results Channel。主 Goroutine 负责向 jobs 发送任务并在完成后从 results 接收结果。

三、高度模型

GPM 是 Go 语言并发编程模型的简称,它由三个核心组件构成:

  1. G(Goroutine):代表执行体或任务,每个 Goroutine 对应一个 G 结构体,这个结构体中包含了程序计数器、栈和调度信息等。Goroutines 是 Go 中轻量级的线程,创建和销毁成本较低,并由运行时系统负责调度。

  2. P(Processor/上下文):处理器,是 Goroutine 执行的实际载体。每个 P 都会与一个内核线程(M)关联,并且维护了一个可运行的 Goroutine 队列。P 负责调度其队列中的 Goroutine 在关联的 M 上执行,并通过 Channel 与其他 P 进行协作和任务交换。

  3. M(Machine/线程):机器,表示操作系统级别的线程。M 负责实际的计算工作,每一个 M 都会绑定到一个 P 上,执行该 P 的任务队列中的 Goroutine。当 M 执行完当前 Goroutine 后,会从 P 的队列中获取下一个待执行的 Goroutine,或者如果 P 的队列为空,则会尝试从全局队列或其他 P 获取任务。

应用示例:
虽然在编写 Go 程序时通常不会直接操作 G、P、M 这些底层对象,但可以编写一段简单的并发代码来展示它们在背后的工作原理。例如,我们可以启动多个 Goroutine 并使用 Channel 进行通信,这将间接体现 GPM 模型的作用:

package main

import (
	"fmt"
	"sync"
)

func worker(id int, jobs <-chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	for j := range jobs {
		fmt.Printf("Worker %d received job %d\n", id, j)
		// 模拟耗时工作
		// ...
	}
}

func main() {
	jobs := make(chan int, 100)
	var wg sync.WaitGroup

	// 创建5个Goroutine作为工作者
	for w := 1; w <= 5; w++ {
		wg.Add(1)
		go worker(w, jobs, &wg)
	}

	// 发送一些任务到Channel
	for i := 0; i < 20; i++ {
		jobs <- i // 将任务放入jobs Channel
	}
	close(jobs) // 发送完所有任务后关闭 Channel

	// 等待所有worker完成任务
	wg.Wait()
	fmt.Println("All jobs done.")
}

在这个例子中:

  • 我们创建了多个 Goroutines (worker) 来处理来自 jobs Channel 的任务。
  • main 函数中的 Goroutine 将任务发送至 Channel 时,Go 运行时调度器根据 GPM 模型自动将这些任务分配给不同的 Worker Goroutine 执行。
  • 使用 sync.WaitGroup 来确保所有 Worker 完成任务后,主 Goroutine 才会继续执行。

虽然这里没有直接涉及到 P 和 M 的具体操作,但它们在后台默默地管理着各个 Goroutine 的创建、执行和切换。GPM 模型确保了即便有大量 Goroutine 在同一时间并发运行,也能高效地利用硬件资源,并且能够正确处理并发环境下的同步问题。


https://www.xamrdz.com/lan/5j71848971.html

相关文章: