假设我有以下结构:
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:]...)
}
}
}此模式不是线程安全的,因此以下测试由于某些争用条件(数组索引超出界限)而失败:
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)
}我刚开始搜索,我想我应该使用频道(?)。因此,实现并行的一种方法是这样:
type ManagerA struct {
Manager
addStringChan chan string
removeStringChan chan string
}
func NewManagerA() *ManagerA {
ma := &ManagerA{
addStringChan: make(chan string),
removeStringChan: make(chan string),
}
go func () {
for {
select {
case msg := <-ma.addStringChan:
ma.AddString(msg)
case msg := <-ma.removeStringChan:
ma.RemoveString(msg)
}
}
}()
return ma
}
func (m* ManagerA) AddStringA(s string) {
m.addStringChan <- s
}
func (m* ManagerA) RemoveStringA(s string) {
m.removeStringChan <- s
}我想公开一个类似于非并发示例的API,因此是AddStringA,RemoveStringA。
这似乎像预期的那样同时工作(虽然我想内部的goroutine也应该在某个时候退出)。我的问题是,有很多额外的样板:
channels
内部goroutine循环,以引导调用。
对我来说有点过分了。有没有一种方法可以简化这个(重构/语法/库)?
我认为实现这一点的最好方法是使用Mutex来代替?但是,是否仍有可能简化这种样板?
发布于 2020-01-14 14:57:44
使用互斥对象是非常惯用的,如下所示:
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()
}你可以用渠道来做这件事,但正如你所注意到的,这是很多额外的工作,但收获并不多。用互斥是我的建议!
发布于 2020-01-14 14:58:07
如果您只需要使对结构线程的访问安全,请使用互斥:
type Manager struct {
sync.Mutex
data []string
}
func (m *Manager) AddString(s string) {
m.Lock()
m.strings = append(m.strings, s)
m.Unlock()
}https://stackoverflow.com/questions/59736242
复制相似问题