第七章:并发编程 1.Goroutines --Go 语言轻松入门

Go 语言中的 Goroutines 是一种轻量级的线程,它允许你以非常低的成本并发执行多个函数或方法。Goroutines 是 Go 并发模型的核心组成部分,与 channels 一起使用可以实现高效的并发编程。

什么是 Goroutines?

1.内存占用小

初始堆栈大小 :每个 Goroutine 的初始堆栈大小非常小,通常是 2KB(Go 1.4 及以后版本)。相比之下,一个典型的线程可能需要几十 KB 到几 MB 的堆栈空间。
动态调整:Goroutine 的堆栈是动态调整的。当 Goroutine 需要更多堆栈空间时,Go 运行时会自动增加堆栈大小;当 Goroutine 结束时,堆栈空间会被回收。

2.创建和切换成本低

创建成本 :创建一个新的 Goroutine 的成本非常低,通常只需要几个微秒。这使得你可以轻松地创建成千上万个 Goroutines。
上下文切换:Goroutine 的上下文切换由 Go 运行时管理,而不是操作系统。这意味着上下文切换的成本更低,因为不需要涉及内核态和用户态之间的切换。

3.多路复用到少量操作系统线程

调度器 :Go 运行时有一个自己的调度器,它将多个 Goroutines 多路复用到少量的操作系统线程上。这样可以减少线程的开销,并且更高效地利用 CPU 资源。
并发模型:通过多路复用,Go 可以在单个操作系统线程上运行多个 Goroutines,从而实现高效的并发执行。

4.无阻塞 I/O

非阻塞 I/O :Go 的标准库提供了许多非阻塞 I/O 操作,这些操作可以与 Goroutines 结合使用,从而避免了传统的线程阻塞问题。例如,net/http 包中的 HTTP 请求处理就是基于 Goroutines 和非阻塞 I/O 的。

具体示例

如何创建 Goroutines

要创建一个 Goroutine,只需在函数调用前加上 go 关键字。例如:

go 复制代码
package main

import (
	"fmt"
	"time"
)

// say 函数接收一个字符串参数 s,并在接下来的500毫秒内,每100毫秒打印一次该字符串。
// 该函数主要用于在一段时间内重复输出某个消息,用于示例演示。
func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond) // 暂停100毫秒以模拟延迟
		fmt.Println(s)                     // 打印传入的字符串参数
	}
}

func main() {
	go say("world") // 在一个新的Goroutine中调用say函数,传入参数"world"
	say("hello")    // 在当前Goroutine中调用say函数,传入参数"hello"
}

在这个例子中,say("world") 在一个新的 Goroutine 中运行,而 say("hello") 在主 Goroutine 中运行。由于主 Goroutine 和新创建的 Goroutine 同时运行,所以你会看到 "hello" 和 "world" 交替打印。

Goroutines 的生命周期

在 Go 语言中,Goroutines 的生命周期管理是并发编程的一个重要方面。理解 Goroutines 的生命周期可以帮助你更好地设计和调试并发程序。以下是 Goroutines 生命周期的详细解释:

1. 创建

  • 启动 :使用 go 关键字可以启动一个新的 Goroutine。例如:

    go 复制代码
    go someFunction()

    这会立即在一个新的 Goroutine 中异步执行 someFunction

  • 初始堆栈大小:每个新创建的 Goroutine 都有一个初始堆栈大小(通常是 2KB),这个堆栈大小可以根据需要动态增长或收缩。

2. 执行

  • 并发执行:一旦 Goroutine 被启动,它就会开始并发执行。多个 Goroutines 可以并行运行,具体取决于可用的 CPU 核心数和 Go 运行时调度器的决策。

  • 调度:Go 运行时有一个自己的调度器,它负责将 Goroutines 分配到操作系统线程上。调度器会根据 Goroutines 的状态和优先级进行调度。

  • 上下文切换:当一个 Goroutine 需要等待 I/O 操作或其他阻塞操作时,调度器会将其挂起,并调度其他可运行的 Goroutines。这种上下文切换非常高效,因为它是用户态的,不需要涉及内核态的切换。

3. 结束

  • 函数返回:当 Goroutine 中的函数返回时,该 Goroutine 就结束了。如果函数返回了一个错误,你可以通过错误处理机制来捕获和处理这些错误。

  • 显式结束 :有时你可能需要显式地结束一个 Goroutine。虽然 Go 语言本身没有提供直接终止 Goroutine 的方法,但可以通过一些技巧来实现这一点,例如使用 context 包中的 Context 来传递取消信号。

4. 等待所有 Goroutines 完成

  • 同步点 :主 Goroutine 可能需要等待所有子 Goroutines 完成后再继续执行。为此,可以使用 sync.WaitGroup 或者 channel 来实现同步。

    • 使用 sync.WaitGroup

      go 复制代码
      package main
      
      import (
      	"fmt"
      	"sync"
      	"time"
      )
      
      // worker 函数接收两个参数:
      // - id: 工作者的唯一标识符
      // - wg: 一个 sync.WaitGroup 指针,用于等待所有工作者完成
      // 该函数模拟了一个工作者的行为,在启动和完成时分别打印消息,并在中间暂停100毫秒以模拟工作时间。
      func worker(id int, wg *sync.WaitGroup) {
      	defer wg.Done()                        // 完成后通知 WaitGroup
      	fmt.Printf("Worker %d starting\n", id) // 打印工作者启动消息
      	time.Sleep(100 * time.Millisecond)     // 暂停100毫秒以模拟工作时间
      	fmt.Printf("Worker %d done\n", id)     // 打印工作者完成消息
      }
      
      func main() {
      	var wg sync.WaitGroup // 初始化一个 WaitGroup
      	numWorkers := 5       // 设置工作者的数量
      
      	for i := 0; i < numWorkers; i++ {
      		wg.Add(1)         // 增加 WaitGroup 的计数
      		go worker(i, &wg) // 在一个新的 Goroutine 中启动工作者
      	}
      
      	wg.Wait()                            // 等待所有工作者完成
      	fmt.Println("All workers are done.") // 打印所有工作者完成的消息
      }
    • 使用 channel

      go 复制代码
      package main
      
      import (
      	"fmt"
      	"time"
      )
      
      // worker 函数接收两个参数:
      // - id: 工作者的唯一标识符
      // - ch: 一个整型通道,用于传递完成信号
      // 该函数模拟了一个工作者的行为,在启动和完成时分别打印消息,并在中间暂停100毫秒以模拟工作时间。
      // 完成后将工作者的ID发送到通道 ch 中。
      func worker(id int, ch chan int) {
      	fmt.Printf("Worker %d starting\n", id) // 打印工作者启动消息
      	time.Sleep(100 * time.Millisecond)     // 暂停100毫秒以模拟工作时间
      	fmt.Printf("Worker %d done\n", id)     // 打印工作者完成消息
      	ch <- id                               // 将工作者的ID发送到通道 ch 中
      }
      
      func main() {
      	ch := make(chan int, 5) // 创建一个缓冲通道,容量为5
      
      	for i := 0; i < 5; i++ {
      		go worker(i, ch) // 在一个新的 Goroutine 中启动工作者
      	}
      
      	for i := 0; i < 5; i++ {
      		<-ch // 从通道 ch 中接收完成信号
      	}
      
      	fmt.Println("All workers are done.") // 打印所有工作者完成的消息
      }

5. 资源回收

  • 垃圾回收:当一个 Goroutine 结束时,其占用的资源(如堆栈空间)会被自动回收。Go 的垃圾回收机制会处理这些资源的释放,确保内存不会泄漏。

6. 错误处理

  • 错误处理 :Goroutines 应该适当地处理错误,并且最好能够通知调用者。可以使用 error 类型、panicrecover 或者 channel 来传递错误信息。

总结

Goroutines 的生命周期包括创建、执行、结束和资源回收。通过合理的设计和使用 sync.WaitGroupchannel,可以有效地管理 Goroutines 的生命周期,确保程序的正确性和性能。理解和掌握这些概念对于编写高效的并发程序至关重要。

相关推荐
你怎么睡得着的!1 小时前
【护网行动-红蓝攻防】第一章-红蓝对抗基础 认识红蓝紫
网络·安全·web安全·网络安全
qwy7152292581634 小时前
13-R数据重塑
服务器·数据库·r语言
anddddoooo6 小时前
域内证书维权
服务器·网络·网络协议·安全·网络安全·https·ssl
Long._.L6 小时前
OpenSSL实验
网络·密码学
Dyan_csdn6 小时前
【Python项目】基于Python的Web漏洞挖掘系统
网络·python·安全·web安全
zhoupenghui1687 小时前
golang时间相关函数总结
服务器·前端·golang·time
孤雪心殇7 小时前
简单易懂,解析Go语言中的Map
开发语言·数据结构·后端·golang·go
努力的小T7 小时前
使用 Docker 部署 Apache Spark 集群教程
linux·运维·服务器·docker·容器·spark·云计算
okok__TXF7 小时前
Rpc导读
网络·网络协议·rpc