同时,如何管理值/状态并避免竞争条件

nba*_*ari 3 go race-condition

如何根据进程启动后发生的事件/条件正确设置/修改值,同时在不创建竞争条件的情况下处理Goroutine

例如,以下“有效(有问题)”,输出为:

ping, foo=true
ping, foo=false
ping, foo=true
ping, foo=true
ping, foo=true
Run Code Online (Sandbox Code Playgroud)

https://play.golang.org/p/Y3FafF-nBc

ping, foo=true
ping, foo=false
ping, foo=true
ping, foo=true
ping, foo=true
Run Code Online (Sandbox Code Playgroud)

但是如果使用-race选项编译或运行,我会得到这个输出:

$ go run -race main.go
ping, foo=true
==================
WARNING: DATA RACE
Write at 0x00c4200761b8 by goroutine 6:
  main.(*test).run()
      /main.go:16 +0x1fb

Previous read at 0x00c4200761b8 by main goroutine:
  main.main()
      /main.go:37 +0x5e

Goroutine 6 (running) created at:
  main.New()
      /main.go:30 +0xd0
  main.main()
      /main.go:35 +0x33
==================
ping, foo=false
ping, foo=true
ping, foo=true
ping, foo=true
Found 1 data race(s)
exit status 66
Run Code Online (Sandbox Code Playgroud)

因此,我想知道我可以使用什么并发模式来更改foogorutine 外部和 gorutine 内部的值,而不会产生竞争条件。

小智 6

你有一些选择:

  • 使用atomic.Value:示例(1)
  • 使用sync.RWMutex:示例(3)
  • 使用sync/atomic:示例(6)
  • 仅使用通道和 goroutines:示例 (7)

另请参阅: 使用 sync.Mutex 还是通道?


1-您可以使用atomic.Value

值提供一致类型值的原子加载和存储。值可以作为其他数据结构的一部分创建。Value 的零值从 Load 返回 nil。调用 Store 后,不得复制 Value。

第一次使用后不得复制值。

像这个工作样本:

// to test the panic use go build -race
package main

import (
    "fmt"
    "sync/atomic"
)

type test struct {
    ch chan string
    atomic.Value
}

func (t *test) run() {
    for {
        select {
        case v := <-t.ch:
            fmt.Printf("%+v, foo=%+v\n", v, t.Load())
            t.Store(false)
        default:
        }
    }
}

func (self *test) Ping() {
    self.ch <- "ping"
}

func New() *test {
    t := &test{
        ch: make(chan string),
    }
    t.Store(false)
    go t.run()
    return t
}

func main() {
    t := New()
    for i := 0; i <= 10; i++ {
        if x, _ := t.Load().(bool); x {
            t.Ping()
        }
        //  time.Sleep(time.Second)
        if i%3 == 0 {
            t.Store(true)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出go build -race

ping, foo=true
ping, foo=false
ping, foo=false
ping, foo=false
ping, foo=false
Run Code Online (Sandbox Code Playgroud)

2- 一点点改进func (t *test) run()

ping, foo=true
ping, foo=false
ping, foo=false
ping, foo=false
ping, foo=false
Run Code Online (Sandbox Code Playgroud)

3- 您可以使用sync.RWMutexand sync.WaitGroup,就像这个工作示例:

func (t *test) run() {
    for v := range t.ch {
        fmt.Printf("%+v, foo=%+v\n", v, t.Load())
        t.Store(false)
    }
}
Run Code Online (Sandbox Code Playgroud)

输出go build -race

ping, foo=true
ping, foo=true
ping, foo=false
ping, foo=true
ping, foo=false
ping, foo=true
Run Code Online (Sandbox Code Playgroud)

4- 所以让我们遵循这种方法https://talks.golang.org/2013/bestpractices.slide#29
原始代码:

// to test the panic use go build -race
package main

import (
    "fmt"
    "sync"
)

type test struct {
    ch  chan string
    foo bool
    sync.RWMutex
    sync.WaitGroup
}

func (t *test) run() {
    for v := range t.ch {
        t.Lock()
        r := t.foo
        t.foo = false
        t.Unlock()
        fmt.Printf("%+v, foo=%+v\n", v, r)

    }
    t.Done()
}

func (self *test) Ping() {
    self.ch <- "ping"
}

func New() *test {
    t := &test{ch: make(chan string)}
    t.Add(1)
    go t.run()
    return t
}

func main() {
    t := New()
    for i := 0; i <= 10; i++ {
        t.RLock()
        r := t.foo
        t.RUnlock()
        if r {
            t.Ping()
        }
        //  time.Sleep(time.Second)
        if i%3 == 0 {
            t.Lock()
            t.foo = true
            t.Unlock()
        }
    }
    close(t.ch)
    t.Wait()
}
Run Code Online (Sandbox Code Playgroud)

5-让我们简化一下:

ping, foo=true
ping, foo=true
ping, foo=false
ping, foo=true
ping, foo=false
ping, foo=true
Run Code Online (Sandbox Code Playgroud)

输出:

running task
running task
server stopping
finishing task
task done
server stopped
Run Code Online (Sandbox Code Playgroud)

6- 样品的简化版本:

package main

import (
    "fmt"
    "time"
)

type Server struct{ quit chan bool }

func NewServer() *Server {
    s := &Server{make(chan bool)}
    go s.run()
    return s
}

func (s *Server) run() {
    for {
        select {
        case <-s.quit:
            fmt.Println("finishing task")
            time.Sleep(time.Second)
            fmt.Println("task done")
            s.quit <- true
            return
        case <-time.After(time.Second):
            fmt.Println("running task")
        }
    }
}
func (s *Server) Stop() {
    fmt.Println("server stopping")
    s.quit <- true
    <-s.quit
    fmt.Println("server stopped")
}

func main() {
    s := NewServer()
    time.Sleep(2 * time.Second)
    s.Stop()
}
Run Code Online (Sandbox Code Playgroud)

输出:

ping, state=1
ping, state=0
ping, state=1
ping, state=0
ping, state=1
ping, state=0
Run Code Online (Sandbox Code Playgroud)

7- 带有通道且不使用Lock()The Go Playground)的工作示例:

package main

import (
    "fmt"
    "time"
)

var quit = make(chan bool)

func main() {
    go run()
    time.Sleep(2 * time.Second)
    fmt.Println("server stopping")

    quit <- true // signal to quit

    <-quit // wait for quit signal

    fmt.Println("server stopped")
}

func run() {
    for {
        select {
        case <-quit:
            fmt.Println("finishing task")
            time.Sleep(time.Second)
            fmt.Println("task done")
            quit <- true
            return
        case <-time.After(time.Second):
            fmt.Println("running task")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

signal false
write true
signal true
ping true
signal false
signal false
write true
signal true
ping true
signal false
signal false
write true
signal true
ping true
signal false
signal false
write true
signal true
ping true
Run Code Online (Sandbox Code Playgroud)