likes
comments
collection
share

Go并发系列:8进阶应用与最佳实践-8.2 实战案例分析

作者站长头像
站长
· 阅读数 46

8.2 实战案例分析

在本节中,我们将通过几个实际的案例,详细分析如何应用前面介绍的并发编程技巧来解决实际问题。这些案例将涵盖常见的并发编程场景,展示如何编写高效、健壮的 Go 并发程序。

案例 1:并发爬虫

在网络爬虫中,通过并发请求可以显著提高抓取网页的速度。我们将通过 Goroutine 池来控制并发度,避免对目标服务器造成过大压力。

需求:编写一个并发爬虫程序,抓取一组 URL 的内容,并统计每个页面的长度。

实现

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "sync"
)

const numWorkers = 5

func fetch(url string, wg *sync.WaitGroup, results chan<- int) {
    defer wg.Done()
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println("Error fetching URL:", url, err)
        results <- 0
        return
    }
    body, err := ioutil.ReadAll(resp.Body)
    resp.Body.Close()
    if err != nil {
        fmt.Println("Error reading response body:", err)
        results <- 0
        return
    }
    results <- len(body)
}

func main() {
    urls := []string{
        "https://golang.org",
        "https://godoc.org",
        "https://play.golang.org",
        "https://gophercises.com",
    }

    results := make(chan int, len(urls))
    var wg sync.WaitGroup

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for _, url := range urls {
                fetch(url, &wg, results)
            }
        }()
    }

    wg.Wait()
    close(results)

    for result := range results {
        fmt.Println("Page length:", result)
    }
}

在这个示例中,我们使用了 Goroutine 池来控制并发度,同时使用 sync.WaitGroup 确保所有请求完成后再继续执行。

案例 2:并发数据处理

在数据处理任务中,通过并发处理可以显著提高处理速度。我们将展示如何使用 sync.WaitGroup 和 Channel 来实现并发数据处理。

需求:编写一个程序,读取一组数据,进行并发处理,并将结果汇总。

实现

package main

import (
    "fmt"
    "sync"
)

func process(data int, wg *sync.WaitGroup, results chan<- int) {
    defer wg.Done()
    // 模拟数据处理
    result := data * 2
    results <- result
}

func main() {
    data := []int{1, 2, 3, 4, 5}
    results := make(chan int, len(data))
    var wg sync.WaitGroup

    for _, d := range data {
        wg.Add(1)
        go process(d, &wg, results)
    }

    wg.Wait()
    close(results)

    for result := range results {
        fmt.Println("Processed result:", result)
    }
}

在这个示例中,我们使用了 sync.WaitGroup 来确保所有 Goroutine 完成,并使用 Channel 来传递处理结果。

案例 3:生产者-消费者模式

生产者-消费者模式是一种常见的并发编程模式,通过 Channel 可以轻松实现这种模式。生产者负责生成数据,消费者负责处理数据。

需求:编写一个生产者-消费者程序,生产者生成一组数据,消费者并发处理数据。

实现

package main

import (
    "fmt"
    "sync"
    "time"
)

func producer(jobs chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for j := 1; j <= 5; j++ {
        jobs <- j
        fmt.Println("Produced job:", j)
        time.Sleep(1 * time.Second)
    }
    close(jobs)
}

func consumer(jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Println("Consumed job:", job)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 5)
    results := make(chan int, 5)
    var wg sync.WaitGroup

    wg.Add(1)
    go producer(jobs, &wg)

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go consumer(jobs, results, &wg)
    }

    wg.Wait()
    close(results)

    for result := range results {
        fmt.Println("Result:", result)
    }
}

在这个示例中,生产者生成数据并发送到 Channel,多个消费者并发处理这些数据,最终汇总处理结果。

案例 4:并发文件下载

在文件下载任务中,通过并发下载可以显著提高下载速度。我们将展示如何使用 Goroutine 和 Channel 实现并发文件下载。

需求:编写一个程序,下载一组文件,并发处理下载任务。

实现

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "sync"
)

func downloadFile(url string, wg *sync.WaitGroup, results chan<- string) {
    defer wg.Done()
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println("Error downloading file:", url, err)
        results <- ""
        return
    }
    defer resp.Body.Close()

    out, err := os.Create("downloaded_file")
    if err != nil {
        fmt.Println("Error creating file:", err)
        results <- ""
        return
    }
    defer out.Close()

    _, err = io.Copy(out, resp.Body)
    if err != nil {
        fmt.Println("Error saving file:", err)
        results <- ""
        return
    }
    results <- url
}

func main() {
    urls := []string{
        "https://example.com/file1",
        "https://example.com/file2",
    }

    results := make(chan string, len(urls))
    var wg sync.WaitGroup

    for _, url := range urls {
        wg.Add(1)
        go downloadFile(url, &wg, results)
    }

    wg.Wait()
    close(results)

    for result := range results {
        if result != "" {
            fmt.Println("Successfully downloaded:", result)
        }
    }
}

在这个示例中,我们并发下载多个文件,并使用 Channel 汇总下载结果。

案例 5:实现一个并发安全的缓存

缓存是应用程序中常见的组件,通过并发访问缓存可以提高性能。我们将展示如何实现一个并发安全的缓存。

需求:编写一个并发安全的缓存,支持并发读写。

实现

package main

import (
    "fmt"
    "sync"
)

type Cache struct {
    data map[string]string
    mu   sync.RWMutex
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    value, ok := c.data[key]
    return value, ok
}

func main() {
    cache := Cache{data: make(map[string]string)}
    var wg sync.WaitGroup

    // 写入缓存
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", i)
            value := fmt.Sprintf("value%d", i)
            cache.Set(key, value)
        }(i)
    }

    // 读取缓存
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", i)
            value, ok := cache.Get(key)
            if ok {
                fmt.Println("Got:", key, "=", value)
            } else {
                fmt.Println("Key not found:", key)
            }
        }(i)
    }

    wg.Wait()
}

在这个示例中,我们使用 sync.RWMutex 来实现一个并发安全的缓存,支持并发读写。

案例 6:实现一个并发的工作队列

工作队列是一种常见的并发编程模式,通过并发执行任务可以提高效率。我们将展示如何实现一个并发的工作队列。

需求:编写一个并发的工作队列,支持多个任务并发执行。

实现

package main

import (
    "fmt"
    "sync"
    "time"
)

type Work struct {
    id int
}

func worker(id int, jobs <-chan Work, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job.id)
        time.Sleep(1 * time.Second) // 模拟处理时间
    }
}

func main() {
    const numWorkers = 3
    jobs := make(chan Work, 10)
    var wg sync.WaitGroup

    for i := 1; i <= numWorkers; i++ {
        wg.Add(1)
        go worker(i, jobs, &wg)
    }

    for j := 1; j <= 10; j++ {
        jobs <- Work{id: j}
        fmt.Printf("Enqueued job %d\n", j)
    }
    close(jobs)

    wg.Wait()
}

在这个示例中,我们创建了一个工作队列,并启动了多个 worker 并发处理队列中的任务。

案例 7:实现一个高效的并发排序算法

排序是数据处理中的基础操作,通过并发执行可以提高排序的效率。我们将展示如何实现一个并发的归并排序算法。

需求:编写一个并发的归并排序算法,实现并发排序。

实现

package main

import (
    "fmt"
    "sort"
    "sync"
)

func merge(left, right []int) []int {
    result := make([]int, 0, len(left)+len(right))
    i, j := 0, 0
    for i < len(left) && j < len(right) {
        if left[i] <= right[j] {
            result = append(result, left[i])
            i++
        } else {
            result = append(result, right[j])
            j++
        }
    }
    result = append(result, left[i:]...)
    result = append(result, right[j:]...)
    return result
}

func concurrentMergeSort(arr []int, wg *sync.WaitGroup) []int {
    defer wg.Done()
    if len(arr) <= 1 {
        return arr
    }

    mid := len(arr) / 2
    var left, right []int

    var leftWg, rightWg sync.WaitGroup
    leftWg.Add(1)
    go func() {
        left = concurrentMergeSort(arr[:mid], &leftWg)
        leftWg.Done()
    }()
    rightWg.Add(1)
    go func() {
        right = concurrentMergeSort(arr[mid:], &rightWg)
        rightWg.Done()
    }()

    leftWg.Wait()
    rightWg.Wait()
    return merge(left, right)
}

func main() {
    arr := []int{38, 27, 43, 3, 9, 82, 10}
    fmt.Println("Unsorted array:", arr)

    var wg sync.WaitGroup
    wg.Add(1)
    sortedArr := concurrentMergeSort(arr, &wg)
    wg.Wait()

    fmt.Println("Sorted array:", sortedArr)
}

在这个示例中,我们实现了一个并发的归并排序算法,通过 Goroutine 并发处理左右子数组,最终合并排序结果。

结论

通过实际案例分析,我们展示了如何在不同场景下应用 Go 的并发编程技巧,包括 Goroutine 池、同步原语、生产者-消费者模式、并发文件下载、并发安全缓存、并发工作队列以及并发排序算法。掌握这些技巧和模式,可以编写出高效、健壮的并发程序,充分利用系统资源,提高程序性能。在接下来的章节中,我们将继续探讨 Go 并发编程的高级特性和最佳实践。

转载自:https://juejin.cn/post/7387305065802645514
评论
请登录