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

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

目录
相关文章
|
7天前
|
监控 算法 Go
Golang深入浅出之-Go语言中的服务熔断、降级与限流策略
【5月更文挑战第4天】本文探讨了分布式系统中保障稳定性的重要策略:服务熔断、降级和限流。服务熔断通过快速失败和暂停故障服务调用来保护系统;服务降级在压力大时提供有限功能以保持整体可用性;限流控制访问频率,防止过载。文中列举了常见问题、解决方案,并提供了Go语言实现示例。合理应用这些策略能增强系统韧性和可用性。
30 0
|
1天前
|
Java Go
一文带你速通go语言指针
Go语言指针入门指南:简述指针用于提升效率,通过地址操作变量。文章作者sharkChili是Java/CSDN专家,维护Java Guide项目。文中介绍指针声明、取值,展示如何通过指针修改变量值及在函数中的应用。通过实例解析如何使用指针优化函数,以实现对原变量的直接修改。作者还邀请读者加入交流群深入探讨,并鼓励关注其公众号“写代码的SharkChili”。
8 0
|
1天前
|
存储 缓存 Java
来聊聊go语言的hashMap
本文介绍了Go语言中的`map`与Java的不同设计思想。作者`sharkChili`是一名Java和Go开发者,同时也是CSDN博客专家及JavaGuide项目的维护者。文章探讨了Go语言`map`的数据结构,包括`count`、`buckets指针`和`bmap`,解释了键值对的存储方式,如何利用内存对齐优化空间使用,并展示了`map`的初始化、插入键值对以及查找数据的源码过程。此外,作者还分享了如何通过汇编查看`map`操作,并鼓励读者深入研究Go的哈希冲突解决和源码。最后,作者提供了一个交流群,供读者讨论相关话题。
9 0
|
2天前
|
Java Go
Go语言学习11-数据初始化
【5月更文挑战第3天】本篇带大家通过内建函数 new 和 make 了解Go语言的数据初始化过程
15 1
Go语言学习11-数据初始化
|
2天前
|
自然语言处理 安全 Java
速通Go语言编译过程
Go语言编译过程详解:从词法分析(生成token)到句法分析(构建语法树),再到语义分析(类型检查、推断、匹配及函数内联)、生成中间码(SSA)和汇编码。最后,通过链接生成可执行文件。作者sharkchili,CSDN Java博客专家,分享技术细节,邀请读者加入交流群。
20 2
|
3天前
|
Java Linux Go
一文带你速通Go语言基础语法
本文是关于Go语言的入门介绍,作者因其简洁高效的特性对Go语言情有独钟。文章首先概述了Go语言的优势,包括快速上手、并发编程简单、设计简洁且功能强大,以及丰富的标准库。接着,文章通过示例展示了如何编写和运行Go代码,包括声明包、导入包和输出语句。此外,还介绍了Go的语法基础,如变量类型(数字、字符串、布尔和复数)、变量赋值、类型转换和默认值。文章还涉及条件分支(if和switch)和循环结构(for)。最后,简要提到了Go函数的定义和多返回值特性,以及一些常见的Go命令。作者计划在后续文章中进一步探讨Go语言的其他方面。
9 0
|
4天前
|
JavaScript 前端开发 Go
Go语言的入门学习
【4月更文挑战第7天】Go语言,通常称为Golang,是由Google设计并开发的一种编程语言,它于2009年公开发布。Go的设计团队主要包括Robert Griesemer、Rob Pike和Ken Thompson,这三位都是计算机科学和软件工程领域的杰出人物。
12 1
|
4天前
|
Go
|
5天前
|
分布式计算 Java Go
Golang深入浅出之-Go语言中的分布式计算框架Apache Beam
【5月更文挑战第6天】Apache Beam是一个统一的编程模型,适用于批处理和流处理,主要支持Java和Python,但也提供实验性的Go SDK。Go SDK的基本概念包括`PTransform`、`PCollection`和`Pipeline`。在使用中,需注意类型转换、窗口和触发器配置、资源管理和错误处理。尽管Go SDK文档有限,生态系统尚不成熟,且性能可能不高,但它仍为分布式计算提供了可移植的解决方案。通过理解和掌握Beam模型,开发者能编写高效的数据处理程序。
133 1
|
5天前
|
算法 关系型数据库 MySQL
Go语言中的分布式ID生成器设计与实现
【5月更文挑战第6天】本文探讨了Go语言在分布式系统中生成全局唯一ID的策略,包括Twitter的Snowflake算法、UUID和MySQL自增ID。Snowflake算法通过时间戳、节点ID和序列号生成ID,Go实现中需处理时间回拨问题。UUID保证全局唯一,但长度较长。MySQL自增ID依赖数据库,可能造成性能瓶颈。选择策略时需考虑业务需求和并发、时间同步等挑战,以确保系统稳定可靠。
112 0