匠心精神 - 良心品质腾讯认可的专业机构-IT人的高薪实战学院

咨询电话:4000806560

Golang并发编程的高级技巧:Mutex、WaitGroup及其应用

Golang并发编程的高级技巧:Mutex、WaitGroup及其应用

在Go语言中,我们可以很方便地编写并发程序。但在面对一些复杂的场景时,我们需要使用一些高级的技巧。本文将介绍Golang中的两个重要工具:Mutex和WaitGroup,并且通过实际应用场景的演示,让读者更好地理解这些高级技巧的应用。

Mutex

在并发编程中,Mutex是我们最常用的同步工具之一。Mutex全称是Mutual Exclusion(互斥),它可以协调不同线程之间对共享资源的访问,避免不同线程之间对同一共享变量的干扰。

Mutex的使用十分简单。我们只需要调用Mutex类型的Lock()方法锁定共享资源,在临界区代码执行完成之后再调用Unlock()方法解锁。下面是一个简单的例子:

```go
import (
    "fmt"
    "sync"
)

var counter int
var mutex sync.Mutex // 定义锁

func increment() {
    mutex.Lock() // 加锁
    counter++
    mutex.Unlock() // 解锁
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    wg.Wait()
    fmt.Println(counter)
}
```

在这个例子中,我们定义了一个全局变量counter,并用Mutex对其进行保护。increment函数是一个临界区,它会增加counter的值。在main函数中,我们启动了1000个goroutine,并且使用WaitGroup等待它们全部执行完毕。最后,我们输出了counter的值。

使用Mutex可以保证counter在并发访问时不会出现竞态条件(race condition),从而得到正确的结果。需要注意的是,如果我们没有使用Mutex,这个程序很可能会输出一个比1000小的值,因为在并发访问时,不同的goroutine可能会同时对counter进行操作。

WaitGroup

WaitGroup是另一个常用的同步工具,它可以等待一组goroutine全部完成之后再执行下一步操作。在之前的例子中,我们已经使用了WaitGroup来等待所有的goroutine执行完毕。

WaitGroup的使用也十分简单。我们只需要调用Add()方法来添加需要等待的goroutine数量,调用Done()方法来表示已完成一个goroutine,然后调用Wait()方法来等待所有goroutine完成。下面是一个简单的例子:

```go
import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    // 模拟工作
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }
    wg.Wait()
    fmt.Println("All workers done")
}
```

在这个例子中,我们定义了一个worker函数,它模拟了一些工作,然后打印出worker的ID和完成信息。在main函数中,我们启动了5个worker goroutine,并使用WaitGroup等待它们全部执行完毕。最后,我们输出"All workers done"。

Mutex和WaitGroup的组合应用

我们已经了解了Mutex和WaitGroup的基本使用方法,下面我们看一个更加复杂的例子,来展示它们的组合应用。在这个例子中,我们需要爬取一些网页,并计算它们的大小和下载时间。这些操作会并行进行,但是为了避免对同一文件进行并发写入,我们需要使用Mutex对文件进行保护。同时,我们也需要使用WaitGroup来等待所有goroutine执行完毕,并计算全部网页的平均大小和平均下载时间。下面是代码:

```go
import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
    "sync"
    "time"
)

var (
    urls = []string{
        "https://www.baidu.com",
        "https://www.qq.com",
        "https://www.taobao.com",
        "https://www.weibo.com",
        "https://www.zhihu.com",
    }
    mutex  sync.Mutex // 定义锁
    wg     sync.WaitGroup
    count  int64   // 记录网页总大小
    times  int64   // 记录下载总时间
    result []*item // 记录每个网页的大小和下载时间
)

type item struct {
    url  string
    size int64
    time int64
}

func download(url string) ([]byte, error) {
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return ioutil.ReadAll(resp.Body)
}

func writeToFile(filename string, data []byte) error {
    mutex.Lock()
    defer mutex.Unlock()
    f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return err
    }
    defer f.Close()
    _, err = f.Write(data)
    if err != nil {
        return err
    }
    return nil
}

func worker(url string) {
    defer wg.Done()
    start := time.Now().UnixNano()
    data, err := download(url)
    if err != nil {
        fmt.Printf("Error downloading %s: %s\n", url, err)
        return
    }
    size := int64(len(data))
    if err := writeToFile("result.txt", data); err != nil {
        fmt.Printf("Error writing to file: %s\n", err)
        return
    }
    end := time.Now().UnixNano()
    elapsed := end - start
    mutex.Lock()
    count += size
    times += elapsed
    result = append(result, &item{url: url, size: size, time: elapsed})
    mutex.Unlock()
}

func main() {
    for _, url := range urls {
        wg.Add(1)
        go worker(url)
    }
    wg.Wait()
    num := int64(len(urls))
    avgSize := float64(count) / float64(num)
    avgTime := float64(times) / float64(time.Millisecond) / float64(num)
    fmt.Printf("Downloaded %d pages, average size: %.2f KB, average time: %.2f ms\n", num, avgSize/1024, avgTime)
    for _, item := range result {
        fmt.Printf("%s: size=%d bytes, time=%.2f ms\n", item.url, item.size, float64(item.time)/float64(time.Millisecond))
    }
}
```

在这个例子中,我们定义了5个URL,并使用WaitGroup启动了5个worker goroutine。在每个worker goroutine中,我们首先使用download函数下载网页,然后使用writeToFile函数将网页写入文件(result.txt),并且使用Mutex对count、times、result等共享资源进行保护。在每个worker goroutine执行完毕后,我们将下载时间、网页大小等信息存储在result变量中。

在main函数中,我们使用WaitGroup等待所有的worker goroutine执行完毕,并计算所有网页的平均大小和平均下载时间。最后,我们输出结果,包括每个网页的下载时间和大小。

总结

在这篇文章中,我们学习了Golang中的两个重要工具:Mutex和WaitGroup。Mutex可以保证同一时刻只有一个goroutine对共享资源进行访问,避免竞态条件,而WaitGroup可以协调多个goroutine之间的执行顺序,并实现多个goroutine的协同工作。

通过一个更加复杂的例子,我们展示了Mutex和WaitGroup的组合应用。在实际应用中,我们可以将它们用于一些需要保护共享资源或需要协调多个goroutine之间的并发程序中。