《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

目录
相关文章
|
21天前
|
并行计算 安全 Go
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念——goroutine和channel。不同于传统的线程模型,Go通过轻量级的goroutine和通信机制channel,实现了高效的并发处理。我们将从基础概念开始,逐步深入到实际应用案例,揭示如何在Go语言中优雅地实现并发控制和数据同步。 ####
|
26天前
|
存储 Go 开发者
Go语言中的并发编程与通道(Channel)的深度探索
本文旨在深入探讨Go语言中并发编程的核心概念和实践,特别是通道(Channel)的使用。通过分析Goroutines和Channels的基本工作原理,我们将了解如何在Go语言中高效地实现并行任务处理。本文不仅介绍了基础语法和用法,还深入讨论了高级特性如缓冲通道、选择性接收以及超时控制等,旨在为读者提供一个全面的并发编程视角。
|
22天前
|
安全 Serverless Go
Go语言中的并发编程:深入理解与实践####
本文旨在为读者提供一个关于Go语言并发编程的全面指南。我们将从并发的基本概念讲起,逐步深入到Go语言特有的goroutine和channel机制,探讨它们如何简化多线程编程的复杂性。通过实例演示和代码分析,本文将揭示Go语言在处理并发任务时的优势,以及如何在实际项目中高效利用这些特性来提升性能和响应速度。无论你是Go语言的初学者还是有一定经验的开发者,本文都将为你提供有价值的见解和实用的技巧。 ####
|
24天前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
26天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
26天前
|
安全 Java Go
Go语言中的并发编程:掌握goroutine与通道的艺术####
本文深入探讨了Go语言中的核心特性——并发编程,通过实例解析goroutine和通道的高效使用技巧,旨在帮助开发者提升多线程程序的性能与可靠性。 ####
|
27天前
|
Go 开发者
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念,重点介绍了goroutines和channels的工作原理及其在实际开发中的应用。文章通过实例演示如何有效地利用这些工具来编写高效、可维护的并发程序,旨在帮助读者理解并掌握Go语言在处理并发任务时的强大能力。 ####
|
25天前
|
算法 安全 程序员
Go语言的并发编程:深入理解与实践####
本文旨在探讨Go语言在并发编程方面的独特优势及其实现机制,通过实例解析关键概念如goroutine和channel,帮助开发者更高效地利用Go进行高性能软件开发。不同于传统的摘要概述,本文将以一个简短的故事开头,引出并发编程的重要性,随后详细阐述Go语言如何简化复杂并发任务的处理,最后通过实际案例展示其强大功能。 --- ###
|
29天前
|
存储 安全 Go
Go 语言以其高效的并发编程能力著称,主要依赖于 goroutines 和 channels 两大核心机制
Go 语言以其高效的并发编程能力著称,主要依赖于 goroutines 和 channels 两大核心机制。本文介绍了这两者的概念、用法及如何结合使用,实现任务的高效并发执行与数据的安全传递,强调了并发编程中的注意事项,旨在帮助开发者更好地掌握 Go 语言的并发编程技巧。
33 2
|
1月前
|
安全 Go 调度
Go语言中的并发编程:掌握goroutines和channels
在现代软件开发中,并发编程已经成为不可或缺的一部分。Go语言以其简洁的语法和强大的并发特性,成为了开发者的首选之一。本文将深入探讨Go语言中的两个核心概念——goroutines和channels,并通过实际代码示例展示如何使用它们来实现高效的并发处理。无论你是初学者还是有经验的开发者,通过本文的学习,你将能够更好地理解和应用Go语言的并发机制,提升你的编程技能。