首页 >后端开发 >Golang >Go 并发:互斥体与通道的示例

Go 并发:互斥体与通道的示例

Mary-Kate Olsen
Mary-Kate Olsen原创
2025-01-08 20:10:54542浏览

Go 并发编程中的计数器同步:Mutex、缓冲通道和非缓冲通道

在 Go 语言中构建并发应用程序时,同步至关重要,以确保安全地访问共享数据。MutexChannel 是 Go 中用于同步的主要工具。

本文探讨了构建安全并发计数器的几种方法。虽然参考文章使用 Mutex 解决了这个问题,但我们还将探讨使用缓冲通道和非缓冲通道的替代方法。

问题描述

我们需要构建一个可以安全地并发使用的计数器。

计数器代码

<code class="language-go">package main

type Counter struct {
    count int
}

func (c *Counter) Inc() {
    c.count++
}

func (c *Counter) Value() int {
    return c.count
}</code>

为了确保代码的并发安全,让我们编写一些测试。

1. 使用 Mutex

Mutex(互斥锁)是一种同步原语,它确保一次只有一个 goroutine 可以访问代码的关键部分。它提供了一种锁机制:当一个 goroutine 锁定 Mutex 时,其他试图锁定它的 goroutine 将被阻塞,直到 Mutex 被解锁。因此,当需要保护共享变量或资源免受竞争条件影响时,通常会使用它。

<code class="language-go">package main

import (
    "sync"
    "testing"
)

func TestCounter(t *testing.T) {
    t.Run("using mutexes and wait groups", func(t *testing.T) {
        counter := Counter{}
        wantedCount := 1000

        var wg sync.WaitGroup
        var mut sync.Mutex

        wg.Add(wantedCount)

        for i := 0; i < wantedCount; i++ {
            go func() {
                defer wg.Done()
                mut.Lock()
                counter.Inc()
                mut.Unlock()
            }()
        }

        wg.Wait()
        if counter.Value() != wantedCount {
            t.Errorf("got %d, want %d", counter.Value(), wantedCount)
        }
    })
}</code>

代码使用了 sync.WaitGroup 来跟踪所有 goroutine 的完成情况,并使用 sync.Mutex 来防止多个 goroutine 同时访问共享计数器。

2. 使用缓冲通道

Go Concurrency: Mutexes vs Channels with Examples

通道是 Go 允许 goroutine 安全通信的一种方式。它们能够在 goroutine 之间传输数据,并通过控制对所传递数据的访问来提供同步。

在本例中,我们将利用通道来阻塞 goroutine,并只允许一个 goroutine 访问共享数据。缓冲通道具有固定的容量,这意味着它们可以在阻塞发送方之前容纳预定义数量的元素。只有当缓冲区已满时,发送方才会被阻塞。

<code class="language-go">package main

import (
    "sync"
    "testing"
)

func TestCounter(t *testing.T) {
    t.Run("using buffered channels and wait groups", func(t *testing.T) {
        counter := Counter{}
        wantedCount := 1000

        var wg sync.WaitGroup
        wg.Add(wantedCount)

        ch := make(chan struct{}, 1)

        ch <- struct{}{} // 允许第一个 goroutine 开始

        for i := 0; i < wantedCount; i++ {
            go func() {
                defer wg.Done()
                <-ch
                counter.Inc()
                ch <- struct{}{}
            }()
        }

        wg.Wait()
        if counter.Value() != wantedCount {
            t.Errorf("got %d, want %d", counter.Value(), wantedCount)
        }
    })
}</code>

代码使用容量为 1 的缓冲通道,允许一次只有一个 goroutine 访问计数器。

3. 使用非缓冲通道

Go Concurrency: Mutexes vs Channels with Examples

非缓冲通道没有缓冲区。它们会阻塞发送方,直到接收方准备好接收数据。这提供了严格的同步,其中数据一次一个地传递到 goroutine 之间。

<code class="language-go">package main

import (
    "sync"
    "testing"
)

func TestCounter(t *testing.T) {
    t.Run("using unbuffered channels and wait groups", func(t *testing.T) {
        counter := Counter{}
        wantedCount := 1000

        var wg sync.WaitGroup
        wg.Add(wantedCount)

        ch := make(chan struct{})

        go func() {
            for i := 0; i < wantedCount; i++ {
                ch <- struct{}{}
            }
            close(ch)
        }()

        for range ch {
            counter.Inc()
            wg.Done()
        }

        if counter.Value() != wantedCount {
            t.Errorf("got %d, want %d", counter.Value(), wantedCount)
        }
    })
}
</code>

代码使用非缓冲通道,确保一次只有一个 goroutine 访问计数器。

4. 使用缓冲通道,不使用 WaitGroup

我们还可以使用缓冲通道而不使用 WaitGroup,例如使用无限循环或另一个通道来跟踪 goroutine 的完成情况。

结论

本文探讨了在 Go 中构建安全并发计数器的不同方法。掌握这些工具以及何时使用它们是编写高效且安全的并发 Go 程序的关键。

参考资源

本文受《Learn Go with tests》中同步章节的启发。

希望本文对您有所帮助!

以上是Go 并发:互斥体与通道的示例的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn