Go 语言 Goroutine - 轻量级线程

简介: Go 语言 Goroutine - 轻量级线程

概述

在 Go 语言里,Goroutine 是一种轻量级的线程实现。

它的出现使得并发编程变得更加容易,无需担心底层线程的复杂管理,Goroutine 会被 Go 的运行时(runtime)智能地调度。

本文将介绍 Goroutine,从基础概念到实际应用,将探索并发编程的魔力。

主要内容包括

Goroutine 的定义与特点

Goroutine 的创建与启动

Goroutine 的同步与等待

Goroutine 的调度与性能制

Goroutine 的错误处理

Goroutine 的最佳实践


 

1. Goroutine 的定义与特点

Goroutine 是 Go 语言中的轻量级线程实现,它由 Go 的运行时系统管理。

与传统线程相比,Goroutine 更加轻量、高效,数以千计的 Goroutine 可以在一个应用程序中同时运行,而不会引起明显的性能开销。


 

2. Goroutine 的创建与启动

2.1

package main
import (  "fmt"  "time")
func main() {  go printNumbers()  go printLetters()
  // 等待一段时间,确保goroutines有足够时间执行  time.Sleep(1 * time.Second)}
func printNumbers() {  for i := 1; i <= 5; i++ {    fmt.Println("Number:", i)    time.Sleep(100 * time.Millisecond)  }}
func printLetters() {  for i := 'a'; i <= 'e'; i++ {    fmt.Println("Letter:", string(i))    time.Sleep(150 * time.Millisecond)  }}

在上面示例中,使用 go 关键字创建了两个 Goroutine,分别用于打印数字和字母。

go 关键字,函数会在一个新的 Goroutine 中异步执行,不会阻塞主程序的运行。


package main
import (  "fmt"  "sync")
func main() {  var wg sync.WaitGroup  wg.Add(2)
  go func() {    defer wg.Done()    fmt.Println("Goroutine 1")  }()
  go func() {    defer wg.Done()    fmt.Println("Goroutine 2")  }()
  wg.Wait()}

在上面示例中,使用了 sync.WaitGroup 来等待两个 Goroutine 的执行完成。

这种方式可以确保所有 Goroutine 都执行完毕后再继续主程序的执行。


 

3. Goroutine 的同步与等待

3

package main
import (  "fmt"  "sync")
func main() {  var wg sync.WaitGroup  wg.Add(2)
  go func() {    defer wg.Done()    fmt.Println("Goroutine 1")  }()
  go func() {    defer wg.Done()    fmt.Println("Goroutine 2")  }()
  wg.Wait()  fmt.Println("All Goroutines have finished.")}

在这个例子中,sync.WaitGroup 用来等待两个 Goroutine 执行完成。

Add(2) 表示需要等待两个 Goroutine 完成,每个 Goroutine 执行完毕时调用 Done() 来通知 WaitGroup。

最后,Wait() 会阻塞主程序,直到所有 Goroutine 都执行完毕。

3.2

package main
import (  "fmt")
func main() {  ch := make(chan string)    go sendData(ch)  msg := <-ch  fmt.Println("Received message:", msg)}
func sendData(ch chan string) {  ch <- "Hello, Goroutine!"}

在这个例子中,创建了一个字符串类型的 Channel,并将其传递给 sendData 函数。

sendData 函数将字符串消息发送到 Channel,主程序通过 <-ch 接收 Channel 中的消息。

这种方式可以实现 Goroutine 之间的简单通信。


 

4. Goroutine 的调度与性能

4.1 Goroutine 的调度器

Go 语言的运行时系统包含了一个 Goroutine 调度器,它负责在多个操作系统线程上调度大量的 Goroutine。

这种调度方式可以使得 Goroutine 在多核处理器上充分利用并发。

4.2 Goroutine 的性能优化

在并发编程中,资源的浪费是一个常见的问题。

合理地控制 Goroutine 的数量,避免创建过多的 Goroutine,可以避免资源的浪费。


 

5. Goroutine 的错误处理

5.1

package main
import (  "errors"  "fmt")
func main() {  ch := make(chan int)
  go func() {    defer func() {      if r := recover(); r != nil {        fmt.Println("Recovered from panic:", r)      }    }()
    if err := performTask(); err != nil {      panic(err)    }
    ch <- 1  }()
  <-ch}
func performTask() error {  return errors.New("went wrong")}

在上面示例中,在 Goroutine 中执行了 performTask() 函数,如果函数返回错误,用 panic() 触发了一个异常。

在 Goroutine 中使用 recover() 可以捕获异常,使得程序不会因为一个 Goroutine 的错误而崩溃。

5.2

package main
import (  "fmt"  "sync"  "time")
func main() {  var wg sync.WaitGroup  done := make(chan struct{})
  for i := 0; i < 3; i++ {    wg.Add(1)    go func(id int) {      defer wg.Done()
      for {        select {        case <-done:          fmt.Printf("Goroutine %d received done signal.\n", id)          return        default:          fmt.Printf("Goroutine %d is working...\n", id)          time.Sleep(500 * time.Millisecond)        }      }    }(i)  }
  time.Sleep(2 * time.Second)  close(done) // 发送关闭信号  wg.Wait()}

在上面例子中,启动了 3 个 Goroutine,它们会一直工作直到接收到 done 通道的关闭信号。

通过 close(done),向所有的 Goroutine 发送了关闭信号,使得它们能够安全、优雅地退出。


 

6. Goroutine 的最佳实践

避免共享内存:Goroutine 之间的通信最好通过 Channel 进行,避免共享内存,从而减少数据竞争的可能性。

合理控制 Goroutine 数量:不要随意创建大量的 Goroutine,合理地控制 Goroutine 的数量可以避免资源浪费。

优雅处理 Goroutine 错误:使用 recover() 捕获 Goroutine 中的异常,确保程序不会因为一个 Goroutine 的错误而崩溃。

确保 Goroutine 退出:Goroutine 应该有退出的机制,确保在不需要时能够安全地退出,避免资源泄露。


 

7. 总结

Goroutine 是 Go 语言并发编程的核心特性,它简化了并发编程的复杂性,提供了一种高效、简洁的并发解决方案。

通过合理的创建、同步和调度,可以编写出高效、稳定的并发程序。

目录
相关文章
|
24天前
|
存储 安全 算法
Go语言是如何支持多线程的
【10月更文挑战第21】Go语言是如何支持多线程的
109 72
|
24天前
|
Go 调度 开发者
Go语言多线程的优势
【10月更文挑战第15天】
15 4
|
24天前
|
安全 Go 调度
goroutine:轻量级线程
【10月更文挑战第15天】
22 3
|
1月前
|
Java API
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
32 0
|
3月前
|
Java 数据库连接 微服务
揭秘微服务架构下的数据魔方:Hibernate如何玩转分布式持久化,实现秒级响应的秘密武器?
【8月更文挑战第31天】微服务架构通过将系统拆分成独立服务,提升了可维护性和扩展性,但也带来了数据一致性和事务管理等挑战。Hibernate 作为强大的 ORM 工具,在微服务中发挥关键作用,通过二级缓存和分布式事务支持,简化了对象关系映射,并提供了有效的持久化策略。其二级缓存机制减少数据库访问,提升性能;支持 JTA 保证跨服务事务一致性;乐观锁机制解决并发数据冲突。合理配置 Hibernate 可助力构建高效稳定的分布式系统。
66 0
|
3月前
|
存储 设计模式 安全
空结构体:Go 语言中的轻量级占位符
【8月更文挑战第31天】
43 0
|
3月前
|
Go 调度
Goroutine:Go语言的轻量级并发机制
【8月更文挑战第31天】
37 0
|
19天前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
33 3
|
20天前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
28 3
|
3月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
131 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库