美文网首页
通过三个例子,学习 Go 语言并发编程的利器 - gorouti

通过三个例子,学习 Go 语言并发编程的利器 - gorouti

作者: _扫地僧_ | 来源:发表于2024-02-20 10:12 被阅读0次

    Go 语言(也称为 Golang)是一门由 Google 开发的编程语言,以其简洁、高效和对并发编程的内置支持而在编程领域享有盛名。

    在 Go 语言中,goroutine 是一项强大的并发特性,用于轻量级线程的创建和管理。

    本文将向没有接触过 Go 语言的朋友,介绍 goroutine 的概念、使用场合,并提供具体的例子以演示其用法。

    1. Goroutine 的概念:

    Goroutine 是 Go 语言中用于并发执行的轻量级线程。与传统的线程相比,goroutine 的创建和销毁成本很低,可以在同一个程序中轻松创建多个 goroutine 而不会导致性能下降。Goroutines 之间通过通道(channel)进行通信,实现了并发编程的简洁和安全。

    2. Goroutine的使用场合:

    俗话说,红粉赠佳人,宝剑送烈士。既然 Goroutine 有如此突出的能力,并发编程领域就是它大显身手的舞台。

    在下列这些应用场景里,我们都能看到 Goroutine 施展拳脚的身影。

    • 并发执行任务: Goroutine 可用于同时执行多个任务,提高程序的性能。
    • 非阻塞 I/O 操作: 在进行 I/O 操作时,可以使用 goroutine 确保其他任务继续执行,而不是同步等待 I/O 完成。
    • 事件驱动编程: Goroutine 可用于处理事件,如监听 HTTP 请求、处理用户输入等。
    • 并发算法: 实现一些需要并行计算的算法,通过 goroutine 可以更轻松地管理并发执行的部分。
    • 定时任务: 使用 goroutine 和定时器可以实现定时执行的任务。

    3. Goroutine 的具体例子:

    我们通过一些具体的例子来说明。

    例子1:简单的并发任务执行

    作为热身,我们通过一些简单的 Hello World 级别的例子来熟悉 Goroutine 的用法。

    源代码如下:

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func printNumbers(wg *sync.WaitGroup) {
        defer wg.Done()
        for i := 1; i <= 5; i++ {
            fmt.Printf("%d ", i)
        }
    }
    
    func printLetters(wg *sync.WaitGroup) {
        defer wg.Done()
        for char := 'a'; char <= 'e'; char++ {
            fmt.Printf("%c ", char)
        }
    }
    
    func main() {
        var wg sync.WaitGroup
    
        wg.Add(2)
        go printNumbers(&wg)
        go printLetters(&wg)
    
        wg.Wait()
    }
    

    将上述源代码另存为一个 .go 文件里,比如取名 1.go, 然后执行命令行 go run 1.go, 看到下面的输出:

    在这个例子中,我们通过关键字 go,创建了两个 goroutine,一个用于打印数字,另一个用于打印字母。sync.WaitGroup 函数调用,用于等待这两个 goroutine 执行完毕。这是 go 语言并发编程里,最常用的同步机制之一。

    例子2:并发下载多个网页

    源代码如下:

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "net/http"
        "sync"
    )
    
    func fetch(url string, wg *sync.WaitGroup) {
        defer wg.Done()
    
        response, err := http.Get(url)
        if err != nil {
            fmt.Printf("Error fetching %s: %v\n", url, err)
            return
        }
        defer response.Body.Close()
    
        body, err := ioutil.ReadAll(response.Body)
        if err != nil {
            fmt.Printf("Error reading response body from %s: %v\n", url, err)
            return
        }
    
        fmt.Printf("Length of %s: %d\n", url, len(body))
    }
    
    func main() {
        var wg sync.WaitGroup
    
        urls := []string{"https://www.baidu.com", "https://cloud.tencent.com/", "https://www.qq.com/"}
    
        for _, url := range urls {
            wg.Add(1)
            go fetch(url, &wg)
        }
        wg.Wait()
    }
    

    执行上面的源代码后,在控制台看到下列输出,打印了三个网站(百度,qq 和腾讯云社区)首页 html 文件的长度(length)。


    这个例子展示了如何使用 goroutine 并发地下载多个网页。每个网页都在单独的goroutine中进行下载,从而减少了完成下载任务所需花费的时间。

    从源代码可以看出,如果是用 Java 这门传统的编程语言完成这个需求的编码工作,我们需要使用 Java 提供的 Thread 类和一些繁琐的同步机制代码。而使用 Go 语言,通过 go fetch(url, &wg) 这一行短小精悍的语言,就可以轻松完成 goroutine 的启动和根据 url 读取 HTML 页面数据的操作,代码大大简化。

    例子3:通过通道实现生产者-消费者模型

    生产者-消费者是操作系统课程里必学的概念之一。我们使用 func 关键字,分别定义了生产者和消费者两个函数。生产者函数,使用 time.Sleep 方法,每隔 0.5 秒钟生产一个正整数序列 1,2,3,4,5. 消费者函数,相应的每隔 0.5 秒钟消费一个正整数。

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func producer(ch chan<- int, wg *sync.WaitGroup) {
        defer wg.Done()
        for i := 1; i <= 5; i++ {
            ch <- i
    fmt.Printf("Produced: %d\n", i)
            time.Sleep(time.Millisecond * 500) // 模拟生产过程的延迟
        }
        close(ch)
    }
    
    func consumer(ch <-chan int, wg *sync.WaitGroup) {
        defer wg.Done()
        for num := range ch {
            fmt.Printf("Consumed: %d\n", num)
            time.Sleep(time.Millisecond * 200) // 模拟消费过程的延迟
        }
    }
    
    func main() {
        var wg sync.WaitGroup
    
        ch := make(chan int, 3) // 创建带缓冲通道,容量为3
    
        wg.Add(2)
        go producer(ch, &wg)
        go consumer(ch, &wg)
    
        wg.Wait()
    }
    

    执行上面的 go 源代码,输出如下,生产一个正整数序列,然后消费,以此类推。


    这个例子演示了如何使用 goroutine 和通道实现生产者-消费者模型。生产者将数据发送到通道,而消费者从通道中接收并处理数据,通过 goroutine 实现了并发的生产和消费过程。

    我们使用了带缓冲的通道,容量为3。带缓冲的通道允许在通道未被完全读取的情况下进行写入,这在生产者和消费者速度不一致时非常有用。

    在生产者函数 producer 里,在ch 是一个只写的整数通道,wg 是一个 sync.WaitGroup 类型的指针,用于等待所有的goroutine完成。在这个函数中,生产者通过 ch <- i 向通道发送整数 i,表示生产了一个产品。然后,通过 fmt.Printf 打印出生产的产品的信息,并通过 time.Sleep 模拟生产过程的延迟。最后,通过 close(ch) 关闭通道,表示生产者不再向通道发送数据。

    再看消费者函数 consumer. ch 是一个只读的整数通道,wg 是一个 sync.WaitGroup 类型的指针。在这个函数中,消费者通过 num := <-ch 从通道接收整数,表示消费了一个产品。然后,通过 fmt.Printf 打印出消费的产品的信息,并通过 time.Sleep 模拟消费过程的延迟。这个循环会一直执行,直到通道被关闭,此时 range ch 将会退出。

    总结

    通过本文介绍的这三个例子,我们可以看到 goroutine 的强大之处。通过goroutine 和 channel 的组合,以及 sync.WaitGroup 的使用,Go 语言提供了强大而简洁的并发编程机制。相信之前有过 Java 编程经验的开发者,对于 Java 和 Go 这两门编程语言,在实现同一需求所需的不同代码量的差异,更是深有体会。

    相关文章

      网友评论

          本文标题:通过三个例子,学习 Go 语言并发编程的利器 - gorouti

          本文链接:https://www.haomeiwen.com/subject/rtugndtx.html