《Go 简易速速上手小册》第5章:并发编程(2024 最新版)(上)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 《Go 简易速速上手小册》第5章:并发编程(2024 最新版)

4001a6010e053ebe5ba4462482fdd0d.png

5.1 Goroutines 的基础 - Go 语言中的轻盈舞者

Ahoy, 并发编程的舞者们!让我们一起深入探索 Go 语言中的 Goroutines —— 这些轻盈的并发执行单位,它们就像是在 CPU 的舞台上轻盈跳跃的舞者。通过 Goroutines,Go 让并发编程变得异常简单和高效,就像是为我们的应用程序注入了一剂速效的能量药剂。

5.1.1 基础知识讲解

Goroutines 的定义

Goroutines 是 Go 语言中实现并发的核心。你可以把它们想象成轻量级的线程,由 Go 运行时管理。与操作系统的线程相比,Goroutines 的启动和销毁成本更低,内存占用也更小,这使得你可以轻松地创建成千上万的 Goroutines。

go function() {
    // 这里是你的代码
}

只需在函数调用前加上 go 关键字,这个函数就会在新的 Goroutine 中异步执行。是的,就是这么简单!

Goroutines 的特点

  • 轻量级:每个 Goroutine 在堆栈上只占用几 KB 的内存。
  • 动态增长的堆栈:Goroutines 的堆栈大小不是固定的,可以根据需要动态增长和缩小。
  • 简单的创建和销毁:创建和销毁 Goroutines 的成本远低于重量级线程。

5.1.2 重点案例:并发下载器

在这个快速发展的互联网时代,下载多个文件是一项常见的任务。利用 Go 语言的 Goroutines,我们可以轻松实现一个并发下载器,这样可以大大加快下载速度,提升用户体验。让我们一起来扩展这个并发下载器的案例,使其更加实用和高效。

功能描述

  1. 并发下载:使用 Goroutines 并发下载多个文件。
  2. 错误处理:捕获下载过程中的错误,并报告。
  3. 进度反馈:实时显示每个文件的下载进度和状态。
  4. 同步等待:使用sync.WaitGroup确保所有下载任务完成后程序才退出。

实现代码

首先,我们模拟一个下载函数,它接收文件名和一个用于报告下载进度的通道:

package main
import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)
// downloadFile 模拟文件下载
func downloadFile(file string, progress chan<- string, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i <= 100; i += rand.Intn(25) {
        progress <- fmt.Sprintf("%s 下载进度: %d%%", file, i)
        time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
    }
    progress <- fmt.Sprintf("%s 下载完成", file)
}

然后,我们创建一个 Goroutine 来处理每个文件的下载,并使用sync.WaitGroup来同步等待所有下载任务完成:

func main() {
    files := []string{"file1.zip", "file2.zip", "file3.zip"}
    var wg sync.WaitGroup
    // 创建一个通道来报告下载进度
    progress := make(chan string)
    // 计数器设置为需要下载的文件数
    wg.Add(len(files))
    for _, file := range files {
        go downloadFile(file, progress, &wg)
    }
    // 启动一个 Goroutine 来打印进度信息
    go func() {
        for p := range progress {
            fmt.Println(p)
        }
    }()
    // 等待所有下载任务完成
    wg.Wait()
    close(progress) // 关闭通道,停止打印进度信息
}

扩展功能

  • 错误处理:我们可以修改downloadFile函数,让它有一定概率模拟下载失败的情况,并通过另一个通道报告错误。
  • 限制并发数:为避免同时启动过多的 Goroutines,我们可以使用带缓冲的通道作为并发限制的信号量。

通过这个扩展案例,我们构建了一个更加健壮和实用的并发下载器,它不仅可以并发下载多个文件,还能处理错误、报告下载进度,并且保证所有任务完成后才退出程序。这个案例展示了 Goroutines 和通道在实际应用中的强大能力,为我们解决并发任务提供了简单有效的工具。现在,就让我们利用这些工具,去构建更多令人激动的并发应用吧!

5.1.3 拓展案例 1:网站健康检查

在维护任何在线服务时,定期检查网站的健康状况是至关重要的。通过并发执行网站健康检查,我们可以在最短的时间内获得多个网站的状态,从而迅速响应可能出现的问题。利用 Go 语言的 Goroutines 和 Channels,我们可以构建一个高效的网站健康检查工具。

功能描述

  1. 并发执行网站健康检查:使用 Goroutines 并发地向多个网站发送请求。
  2. 收集并报告结果:收集每个网站的健康检查结果,并汇总报告。

实现代码

首先,定义一个简单的函数来检查单个网站的健康状况:

package main
import (
    "fmt"
    "net/http"
    "sync"
    "time"
)
// checkWebsite 检查网站健康状况
func checkWebsite(url string, wg *sync.WaitGroup, results chan<- string) {
    defer wg.Done()
    start := time.Now()
    resp, err := http.Get(url)
    duration := time.Since(start)
    if err != nil || resp.StatusCode != 200 {
        results <- fmt.Sprintf("[失败] %s 耗时 %s", url, duration)
        return
    }
    results <- fmt.Sprintf("[成功] %s 状态码 %d 耗时 %s", url, resp.StatusCode, duration)
}

然后,使用 Goroutines 并发执行多个网站的健康检查,并使用sync.WaitGroup同步等待所有检查任务完成:

func main() {
    websites := []string{
        "https://www.google.com",
        "https://www.github.com",
        "https://www.stackoverflow.com",
        "https://golang.org",
        "https://www.example.com",
    }
    var wg sync.WaitGroup
    results := make(chan string, len(websites))
    wg.Add(len(websites))
    for _, url := range websites {
        go checkWebsite(url, &wg, results)
    }
    go func() {
        wg.Wait()
        close(results)
    }()
    // 打印检查结果
    for result := range results {
        fmt.Println(result)
    }
}

扩展功能

  • 超时控制:为http.Get请求添加超时控制,防止某些网站响应过慢影响整体检查进程。
  • 重试机制:对于检查失败的网站,可以实现重试机制,以确保偶发的网络问题不会导致误报。

通过这个扩展案例,我们构建了一个可以并发执行网站健康检查的工具,它能够快速收集和报告多个网站的状态。利用 Go 语言的并发特性,我们的工具不仅执行效率高,而且代码结构清晰简洁。这种并发模式的应用,在开发高效且可靠的网络服务和工具时非常有价值。现在,就让我们继续探索 Go 语言的并发世界,开发更多强大的应用吧!

5.1.4 拓展案例 2:并发日志处理器

拓展案例 2:并发日志处理器

在大型系统中,日志是监控系统健康、诊断问题的重要手段。随着系统规模的扩大,日志量也会急剧增加。使用并发日志处理器,我们可以高效地从多个来源并发地收集、处理日志,提高日志处理的速度和效率。

功能描述
  1. 并发收集日志:使用 Goroutines 并发地从多个日志来源(如文件、网络等)收集日志。
  2. 日志处理:对收集到的日志执行一系列处理操作,如过滤、格式化。
  3. 日志聚合:将处理后的日志聚合到一个中心位置,以便分析和存储。
实现代码

首先,定义一个模拟的日志收集函数,假设日志来自不同的文件:

package main
import (
    "fmt"
    "sync"
    "time"
)
// collectLogs 从指定的日志来源收集日志
func collectLogs(source string, wg *sync.WaitGroup, logChan chan<- string) {
    defer wg.Done()
    // 模拟从不同来源收集日志的时间消耗
    time.Sleep(time.Duration(1+rand.Intn(5)) * time.Second)
    logMsg := fmt.Sprintf("日志来自 %s: 日志内容", source)
    logChan <- logMsg
}

接着,实现并发的日志收集和处理逻辑:

func main() {
    logSources := []string{"文件1", "文件2", "网络流", "数据库"}
    var wg sync.WaitGroup
    logChan := make(chan string, len(logSources))
    // 并发从各个日志来源收集日志
    wg.Add(len(logSources))
    for _, source := range logSources {
        go collectLogs(source, &wg, logChan)
    }
    // 启动一个 Goroutine 来处理日志
    go func() {
        for logMsg := range logChan {
            fmt.Println("处理日志:", logMsg)
            // 这里可以添加更复杂的日志处理逻辑
        }
    }()
    // 等待所有日志收集任务完成
    wg.Wait()
    close(logChan) // 关闭通道,结束日志处理 Goroutine
}
扩展功能
  • 日志过滤:可以在处理日志的 Goroutine 中加入过滤逻辑,只保留符合特定条件的日志。
  • 日志格式化:对日志进行格式化处理,例如转换为 JSON 格式,以便于后续处理和存储。
  • 错误处理:增加错误处理逻辑,确保日志收集和处理过程中的错误能够被妥善处理。

通过这个扩展案例,我们构建了一个能够高效处理大量日志的并发日志处理器。利用 Go 语言的并发特性,我们的处理器可以轻松应对来自不同来源的日志,提高了日志处理的速度和灵活性。这种并发处理模式对于构建高性能的日志系统来说是非常有价值的。现在,让我们继续探索 Go 语言的并发特性,开发更多强大且高效的系统吧!

5.2 Channels 的使用 - Go 语言中的通信艺术

Ahoy,并发航海者们!进入 Go 的并发世界后,我们已经学会了如何让多个 Goroutines 舞动起来。现在,是时候让这些舞者学会如何交流了。在 Go 语言中,Channels 是 Goroutines 之间沟通的红绸带,让并发的执行流可以优雅地传递消息。

5.2.1 基础知识讲解

Channels 的定义

Channels 是 Go 语言中的一种类型,用于在 Goroutines 之间进行通信和数据的传递。你可以将 Channel 想象为一条河流,数据就像是河流中的水,可以从一个地方流向另一个地方。

ch := make(chan int)

上面的代码创建了一个传递int类型数据的 Channel。

Channels 的发送和接收

向 Channel 发送数据和从 Channel 接收数据,都使用<-运算符。

ch <- 42 // 向 Channel 发送数据
v := <-ch // 从 Channel 接收数据并赋值给 v

关闭 Channels

当你完成了 Channel 的使用,可以关闭它来防止发生更多的数据发送。接收操作可以继续进行,直到 Channel 中的现有数据都被接收完毕。

close(ch)

5.2.2 重点案例:任务分发系统

在许多应用场景中,我们需要将大量任务分发给不同的工作单元进行并发处理,然后收集和汇总处理结果。这不仅可以显著提高任务处理的效率,还能优化资源的利用。通过使用 Go 语言的 Goroutines 和 Channels,我们可以构建一个高效的任务分发系统。

功能描述

  1. 并发任务处理:创建多个工作 Goroutines 并发处理任务。
  2. 任务队列:使用 Channel 作为任务队列,分发任务给工作 Goroutines。
  3. 结果收集:工作 Goroutines 处理完成后,通过另一个 Channel 将结果返回。

实现代码

首先,定义TaskResult的结构体,以及一个模拟的任务处理函数:

package main
import (
    "fmt"
    "sync"
    "time"
)
type Task struct {
    ID   int
    Data string
}
type Result struct {
    TaskID int
    Output string
}
// 模拟任务处理函数
func processTask(data string) string {
    // 模拟处理时间
    time.Sleep(time.Second)
    return data + " processed"
}

实现工作 Goroutines,从任务 Channel 接收任务,处理任务,并将结果发送到结果 Channel:

func worker(taskChan <-chan Task, resultChan chan<- Result, wg *sync.WaitGroup) {
    defer wg.Done()
    for task := range taskChan {
        // 处理任务
        output := processTask(task.Data)
        resultChan <- Result{TaskID: task.ID, Output: output}
    }
}

构建任务分发和结果收集的主逻辑:

func main() {
    // 创建任务和结果的 Channels
    taskChan := make(chan Task, 10)
    resultChan := make(chan Result, 10)
    // 使用 WaitGroup 等待所有工作 Goroutines 完成
    var wg sync.WaitGroup
    // 启动工作 Goroutines
    for w := 1; w <= 3; w++ {
        wg.Add(1)
        go worker(taskChan, resultChan, &wg)
    }
    // 分发任务
    for i := 1; i <= 5; i++ {
        taskChan <- Task{ID: i, Data: fmt.Sprintf("Task %d", i)}
    }
    close(taskChan)
    // 启动一个 Goroutine 等待所有工作完成后关闭结果 Channel
    go func() {
        wg.Wait()
        close(resultChan)
    }()
    // 收集并打印处理结果
    for result := range resultChan {
        fmt.Printf("Task %d: %s\n", result.TaskID, result.Output)
    }
}

通过这个扩展案例,我们构建了一个灵活且高效的任务分发系统。它展示了如何利用 Go 语言的并发特性来并行处理任务,并通过 Channels 安全地在 Goroutines 之间传递数据。这种模式非常适合于处理那些可以并行化的独立任务,极大地提高了任务处理的速度和效率。现在,就让我们继续探索 Go 语言的并发世界,发现更多的可能性吧!

5.2.3 拓展案例 1:数据流处理

拓展案例 1:数据流处理

数据流处理是一种常见的编程模式,特别适用于需要对数据进行一系列转换或计算的场景。在 Go 语言中,我们可以利用 Channels 和 Goroutines 构建一个高效的数据流处理管道(pipeline),这样可以并发地对数据进行处理,提高处理效率。

功能描述
  1. 创建处理管道:使用 Channels 将一系列的数据处理步骤连接起来,形成一个处理管道。
  2. 并发数据处理:每个处理步骤都运行在独立的 Goroutine 中,以实现并发处理。
  3. 灵活的数据传递:通过 Channels 在管道的各个阶段之间传递数据。
实现代码

首先,定义几个简单的数据处理函数,每个函数代表管道中的一个处理阶段:

package main
import (
    "fmt"
    "strings"
    "time"
)
// stage1:将字符串转换为大写
func stage1(input <-chan string) <-chan string {
    output := make(chan string)
    go func() {
        for s := range input {
            output <- strings.ToUpper(s)
        }
        close(output)
    }()
    return output
}
// stage2:在字符串后添加特定后缀
func stage2(input <-chan string) <-chan string {
    output := make(chan string)
    go func() {
        for s := range input {
            output <- s + " PROCESSED"
        }
        close(output)
    }()
    return output
}
// stage3:模拟耗时操作,如写入数据库
func stage3(input <-chan string) <-chan string {
    output := make(chan string)
    go func() {
        for s := range input {
            // 模拟耗时操作
            time.Sleep(1 * time.Second)
            output <- s + " -> SAVED"
        }
        close(output)
    }()
    return output
}

接着,构建并运行数据流处理管道:

func main() {
    // 初始数据源
    input := make(chan string)
    go func() {
        for _, s := range []string{"data1", "data2", "data3"} {
            input <- s
        }
        close(input)
    }()
    // 构建处理管道
    stage1Output := stage1(input)
    stage2Output := stage2(stage1Output)
    stage3Output := stage3(stage2Output)
    // 收集并打印处理结果
    for result := range stage3Output {
        fmt.Println(result)
    }
}
扩展功能
  • 错误处理:可以在管道的每个阶段添加错误处理逻辑,确保处理过程的健壮性。
  • 动态管道构建:根据实际需求动态地添加或移除处理阶段,使管道更加灵活。

通过这个扩展案例,我们构建了一个并发的数据流处理管道,它展示了如何使用 Go 语言的 Channels 和 Goroutines 来实现数据的并发处理。这种模式非常适合处理大量数据或进行复杂的数据转换和计算任务,能够显著提高处理效率。利用这种模式,我们可以轻松地构建出灵活、高效的数据处理应用。现在,让我们继续探索 Go 语言并发编程的强大功能,开发更多高效的应用吧!


《Go 简易速速上手小册》第5章:并发编程(2024 最新版)(下)+https://developer.aliyun.com/article/1486992

目录
相关文章
|
2月前
|
缓存 NoSQL Go
通过 SingleFlight 模式学习 Go 并发编程
通过 SingleFlight 模式学习 Go 并发编程
|
4月前
|
Go
go语言并发编程(五) ——Context
go语言并发编程(五) ——Context
|
4月前
|
存储 缓存 Go
Go语言并发编程(三)——初窥管道
Go语言并发编程(三)——初窥管道
|
26天前
|
安全 大数据 Go
深入探索Go语言并发编程:Goroutines与Channels的实战应用
在当今高性能、高并发的应用需求下,Go语言以其独特的并发模型——Goroutines和Channels,成为了众多开发者眼中的璀璨明星。本文不仅阐述了Goroutines作为轻量级线程的优势,还深入剖析了Channels作为Goroutines间通信的桥梁,如何优雅地解决并发编程中的复杂问题。通过实战案例,我们将展示如何利用这些特性构建高效、可扩展的并发系统,同时探讨并发编程中常见的陷阱与最佳实践,为读者打开Go语言并发编程的广阔视野。
|
2月前
|
存储 安全 Go
Go 并发编程精粹:掌握通道(channels)的艺术
Go 并发编程精粹:掌握通道(channels)的艺术
|
2月前
|
Go 开发者
使用Go语言进行高效并发编程
【8月更文挑战第9天】Go语言的并发模型以其简洁和高效著称,通过goroutines和channels,开发者可以轻松地编写出高性能的并发程序。此外,Go标准库还提供了丰富的并发原语和工具,如WaitGroup和Context,进一步简化了并发编程的复杂性。掌握Go语言的并发编程技巧,对于开发高性能、高并发的应用至关重要。希望本文能帮助你更好地理解和使用Go语言进行并发编程。
|
2月前
|
设计模式 安全 Go
深入探索 Go 语言中的并发编程
并发编程是现代软件开发中的重要组成部分,尤其是在多核处理器普及的今天,如何高效地利用硬件资源成为关键。Go 语言以其内置的并发支持而闻名,使得开发者能够轻松构建高性能、可扩展的应用程序。本文将深入探讨 Go 语言中的并发模型,涵盖 Goroutines、Channels 的使用,并讨论常见的并发陷阱及如何避免。
|
4月前
|
Go
如何在Go中进行文件操作以及如何使用协程来实现并发编程
如何在Go中进行文件操作以及如何使用协程来实现并发编程
45 2
|
4月前
|
Linux Go 索引
go语言并发编程(四) ——再探管道
go语言并发编程(四) ——再探管道
|
4月前
|
存储 Go
go语言并发编程(二)——锁
go语言并发编程(二)——锁