Go:再次温故并发编程

Go 语言自诞生之初便以其原生的并发编程支持作为主要卖点之一。通过轻量级的线程(goroutines)和强大的通信机制(channels),Go 不仅提供了一种高效处理并行任务的方法,还简化了并发控制和状态管理的复杂性。本文将详细介绍 Go 中的并发机制,探讨 goroutine 的使用技巧,channel 的各种操作模式,以及如何通过这些工具实现高效的并发程序。

1. Goroutines:轻量级线程

Goroutines 是 Go 中实现并发的核心。与传统的线程相比,goroutines 是由 Go 运行时管理的,拥有更小的堆栈内存(通常几 KB),且创建和销毁的开销小,允许程序同时运行成千上万的 goroutines。

1.1 启动与运行 Goroutine

在 Go 中,启动一个 goroutine 非常简单。我们只需要在函数调用前加上 go 关键字。例如:

go 复制代码
package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

在这个例子中,main 函数中的 go say("world") 启动了一个新的 goroutine。这意味着 say("world") 与 say("hello") 将并发执行。

1.2 Goroutine 的调度

Go 使用了基于 M:N 调度模型(多个 goroutines 可以在多个 OS 线程上运行)。Go 调度器在运行时分配 goroutines 到可用的逻辑处理器,然后将这些逻辑处理器绑定到单个 OS 线程。

2. Channels:协程间的通信

Channels 是 Go 中用于在 goroutines 之间安全传递数据的机制。通过使用 channels,开发者可以避免传统并发程序中常见的竞态条件和锁问题。

2.1 创建和使用 Channel

我们可以使用 make 函数创建一个新的 channel:

go 复制代码
ch := make(chan int)

Goroutines 通过 channel 发送和接收数据,操作符为 <-。例如:

go 复制代码
ch <- v // 发送 v 到 channel ch
v := <-ch // 从 ch 接收数据并赋值给 v

2.2 Channel 的阻塞行为

Channels 的重要特性之一是它们的阻塞行为。如果一个 goroutine 试图从一个空的 channel 接收数据,它会阻塞,直到有数据可读。同样,如果一个 goroutine 试图向一个满的(或未准备好的接收者)channel 发送数据,它也会阻塞,直到数据被读取。

3. 实战案例:并发的 Web 爬虫

考虑一个简单的 Web 爬虫,它使用 goroutines 并发地抓取网页,并通过一个共享的 channel 传递数据:

go 复制代码
package main

import (
    "fmt"
    "net/http"
    "time"
)

func fetch(url string, ch chan<- string) {
    start := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprintf("error: %s", err)
        return
    }
    ch <- fmt.Sprintf("%s, %s, %dms", url, resp.Status, time.Since(start).Milliseconds())
}

func main() {
    urls := []string{
        "https://www.google.com",
        "https://www.baidu.com",
        "https://www.amazon.com",
    }

    ch := make(chan string)
    for _, url := range urls {
        go fetch(url, ch)
    }

    for range urls {
        fmt.Println(<-ch)
    }
}

总结

通过 goroutines 和 channels,Go 为并发编程提供了强大而简洁的工具。这些特性使得开发并行程序和管理复杂的并发状态变得更加容易。随着对这些机制的深入了解,我们将能够更有效地利用现代多核硬件,编写高效、可靠的 Go 程序。

相关推荐
灰子学技术26 分钟前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
Gogo8161 小时前
BigInt 与 Number 的爱恨情仇,为何大佬都劝你“能用 Number 就别用 BigInt”?
后端
fuquxiaoguang1 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
毕设源码_廖学姐2 小时前
计算机毕业设计springboot招聘系统网站 基于SpringBoot的在线人才对接平台 SpringBoot驱动的智能求职与招聘服务网
spring boot·后端·课程设计
mtngt113 小时前
AI DDD重构实践
go
野犬寒鸦4 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
逍遥德4 小时前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
MX_93595 小时前
Spring的bean工厂后处理器和Bean后处理器
java·后端·spring
程序员泠零澪回家种桔子6 小时前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构
源代码•宸6 小时前
大厂技术岗面试之谈薪资
经验分享·后端·面试·职场和发展·golang·大厂·职级水平的薪资