去并发切片访问

时间:2013-04-18 16:46:52

标签: concurrency go

我正在Go中进行一些流处理,并试图弄清楚如何在没有锁的情况下执行“Go way”。

这个人为的例子说明了我面临的问题。

  • 我们一次只能获得一个thing
  • 有一个goroutine将它们缓冲到一个名为things的切片中。
  • things变满len(things) == 100时,会以某种方式对其进行处理并重置
  • n个并发goroutine需要在things完整之前访问
  • 从其他goroutines访问“不完整”things是不可预测的。
  • doSomethingWithPartialdoSomethingWithComplete都不需要改变things

代码:

var m sync.Mutex
var count int64
things := make([]int64, 0, 100)

// slices of data are constantly being generated and used
go func() {
  for {
    m.Lock()
    if len(things) == 100 {
      // doSomethingWithComplete does not modify things
      doSomethingWithComplete(things)
      things = make([]int64, 0, 100)
    }
    things = append(things, count)
    m.Unlock()
    count++
  }
}()

// doSomethingWithPartial needs to access the things before they're ready
for {
  m.Lock()
  // doSomethingWithPartial does not modify things
  doSomethingWithPartial(things)
  m.Unlock()
}
  1. 我知道切片是不可变的,所以这意味着我可以删除互斥锁并期望它仍然可以工作(我假设没有)

  2. 如何重构此操作以使用频道而不是互斥锁。

  3. 编辑:以下是我提出的不使用互斥锁的解决方案

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func Incrementor() chan int {
        ch := make(chan int)
        go func() {
            count := 0
            for {
                ch <- count
                count++
            }
        }()
        return ch
    }
    
    type Foo struct {
        things   []int
        requests chan chan []int
        stream   chan int
        C        chan []int
    }
    
    func NewFoo() *Foo {
        foo := &Foo{
            things:   make([]int, 0, 100),
            requests: make(chan chan []int),
            stream:   Incrementor(),
            C:        make(chan []int),
        }
        go foo.Launch()
        return foo
    }
    
    func (f *Foo) Launch() {
        for {
            select {
            case ch := <-f.requests:
                ch <- f.things
            case thing := <-f.stream:
                if len(f.things) == 100 {
                    f.C <- f.things
                    f.things = make([]int, 0, 100)
                }
                f.things = append(f.things, thing)
            }
        }
    }
    
    func (f *Foo) Things() []int {
        ch := make(chan []int)
        f.requests <- ch
        return <-ch
    }
    
    func main() {
    
        foo := NewFoo()
    
        var wg sync.WaitGroup
        wg.Add(10)
    
        for i := 0; i < 10; i++ {
            go func(i int) {
                time.Sleep(time.Millisecond * time.Duration(i) * 100)
                things := foo.Things()
                fmt.Println("got things:", len(things))
                wg.Done()
            }(i)
        }
    
        go func() {
            for _ = range foo.C {
                // do something with things
            }
        }()
    
        wg.Wait()
    }
    

1 个答案:

答案 0 :(得分:1)

应该注意的是,“Go way”可能只是为了使用互斥锁。了解如何使用通道进行操作很有趣,但对于此特定问题,互斥锁可能更简单,更容易推理。