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 语言并发编程的核心特性,它简化了并发编程的复杂性,提供了一种高效、简洁的并发解决方案。

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

目录
相关文章
|
2月前
|
存储 安全 算法
Go语言是如何支持多线程的
【10月更文挑战第21】Go语言是如何支持多线程的
119 72
|
1月前
|
JSON Go 开发者
go-carbon v2.5.0 发布,轻量级、语义化、对开发者友好的 golang 时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 Golang 时间处理库,提供了对时间穿越、时间差值、时间极值、时间判断、星座、星座、农历、儒略日 / 简化儒略日、波斯历 / 伊朗历的支持。
39 4
|
2月前
|
Go 调度 开发者
Go语言多线程的优势
【10月更文挑战第15天】
25 4
|
2月前
|
安全 Go 调度
goroutine:轻量级线程
【10月更文挑战第15天】
34 3
|
2月前
|
Java API
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
43 0
|
4月前
|
Java 数据库连接 微服务
揭秘微服务架构下的数据魔方:Hibernate如何玩转分布式持久化,实现秒级响应的秘密武器?
【8月更文挑战第31天】微服务架构通过将系统拆分成独立服务,提升了可维护性和扩展性,但也带来了数据一致性和事务管理等挑战。Hibernate 作为强大的 ORM 工具,在微服务中发挥关键作用,通过二级缓存和分布式事务支持,简化了对象关系映射,并提供了有效的持久化策略。其二级缓存机制减少数据库访问,提升性能;支持 JTA 保证跨服务事务一致性;乐观锁机制解决并发数据冲突。合理配置 Hibernate 可助力构建高效稳定的分布式系统。
78 0
|
4月前
|
存储 设计模式 安全
空结构体:Go 语言中的轻量级占位符
【8月更文挑战第31天】
52 0
|
4天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
17 1
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
60 1
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
32 3