使用 go 通道使结构线程安全

Tom*_*mos 3 go channels

假设我有以下结构:

package manager

type Manager struct {
    strings []string
}

func (m *Manager) AddString(s string) {
    m.strings = append(m.strings, s)
}

func (m *Manager) RemoveString(s string) {
    for i, str := range m.strings {
        if str == s {
            m.strings = append(m.strings[:i], m.strings[i+1:]...)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

此模式不是线程安全的,因此以下测试由于某些竞争条件(数组索引超出范围)而失败:

func TestManagerConcurrently(t *testing.T) {
    m := &manager.Manager{}
    wg := sync.WaitGroup{}
    for i:=0; i<100; i++ {
        wg.Add(1)
        go func () {
            m.AddString("a")
            m.AddString("b")
            m.AddString("c")
            m.RemoveString("b")
            wg.Done()
        } ()
    }
    wg.Wait()

    fmt.Println(m)
}
Run Code Online (Sandbox Code Playgroud)

我是 Go 的新手,通过谷歌搜索,我想我应该使用渠道 (?)。因此,使这种并发的一种方法是这样的:

func TestManagerConcurrently(t *testing.T) {
    m := &manager.Manager{}
    wg := sync.WaitGroup{}
    for i:=0; i<100; i++ {
        wg.Add(1)
        go func () {
            m.AddString("a")
            m.AddString("b")
            m.AddString("c")
            m.RemoveString("b")
            wg.Done()
        } ()
    }
    wg.Wait()

    fmt.Println(m)
}
Run Code Online (Sandbox Code Playgroud)

我想公开一个类似于非并发示例的 API,因此是 AddStringA、RemoveStringA。

这似乎同时按预期工作(尽管我猜内部 goroutine 也应该在某个时候退出)。我的问题是有很多额外的样板:

  • 需要定义和初始化通道
  • 使用 select 定义内部 goroutine 循环
  • 将函数映射到通道调用

对我来说似乎有点多。有没有办法简化这个(重构/语法/库)?

我认为实现这一点的最佳方法是改用互斥锁?但是还有可能简化这种样板吗?

Nic*_*ood 8

使用互斥锁将是非常惯用的,如下所示:

type Manager struct {
    mu      sync.Mutex
    strings []string
}

func (m *Manager) AddString(s string) {
    m.mu.Lock()
    m.strings = append(m.strings, s)
    m.mu.Unlock()
}

func (m *Manager) RemoveString(s string) {
    m.mu.Lock()
    for i, str := range m.strings {
        if str == s {
            m.strings = append(m.strings[:i], m.strings[i+1:]...)
        }
    }
    m.mu.Unlock()
}
Run Code Online (Sandbox Code Playgroud)

您可以使用渠道来做到这一点,但正如您所指出的,这是一项额外的工作,但收益不大。我的建议是使用互斥锁!


Bur*_*dar 5

如果您只需要访问 struct 线程安全,请使用互斥锁:

type Manager struct {
   sync.Mutex
   data []string
}

func (m *Manager) AddString(s string) {
    m.Lock()
    m.strings = append(m.strings, s)
    m.Unlock()
}
Run Code Online (Sandbox Code Playgroud)

  • 我建议不要像这样嵌入`sync.Mutex`。相反,请将其添加为未导出字段。 (2认同)