-2

How can I elegantly do it in go?

In python I could use attribute like this:

def function():
    function.counter += 1
function.counter = 0

Does go have the same opportunity?

Kenenbek Arzymatov
  • 8,439
  • 19
  • 58
  • 109

3 Answers3

6

For example,

count.go:

package main

import (
    "fmt"
    "sync"
    "time"
)

type Count struct {
    mx    *sync.Mutex
    count int64
}

func NewCount() *Count {
    return &Count{mx: new(sync.Mutex), count: 0}
}

func (c *Count) Incr() {
    c.mx.Lock()
    c.count++
    c.mx.Unlock()
}

func (c *Count) Count() int64 {
    c.mx.Lock()
    count := c.count
    c.mx.Unlock()
    return count
}

var fncCount = NewCount()

func fnc() {
    fncCount.Incr()
}

func main() {
    for i := 0; i < 42; i++ {
        go fnc()
    }
    time.Sleep(time.Second)
    fmt.Println(fncCount.Count())
}

Output:

$ go run count.go
42

Also, run the race detector,

$ go run -race count.go
42

See:

Introducing the Go Race Detector.

Benign data races: what could possibly go wrong?.

The Go Memory Model


And here's a racy solution (@maerics answer is racy for the same reason),

package main

import (
    "fmt"
    "time"
)

var fncCount = 0

func fnc() {
    fncCount++
}

func main() {
    for i := 0; i < 42; i++ {
        go fnc()
    }
    time.Sleep(time.Second)
    fmt.Println(fncCount)
}

Output:

$ go run racer.go
39

And, with the race detector,

Output:

$ go run -race racer.go
==================
WARNING: DATA RACE
Read at 0x0000005b5380 by goroutine 7:
  main.fnc()
      /home/peter/gopath/src/so/racer.go:11 +0x3a

Previous write at 0x0000005b5380 by goroutine 6:
  main.fnc()
      /home/peter/gopath/src/so/racer.go:11 +0x56

Goroutine 7 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:16 +0x4f

Goroutine 6 (finished) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:16 +0x4f
==================
42
Found 1 data race(s)
exit status 66
$ 
peterSO
  • 158,998
  • 31
  • 281
  • 276
  • You can use the atomic package and get the same safety with less code. – navossoc Nov 21 '17 at 22:14
  • @navossoc: Not true. For multiple goroutines without synchronization events, you will read between zero and the written count. For example, `atomic.LoadUint64(&a)` may always be zero for `go func() { for { atomic.AddUint64(&a, uint64(1)) } }()`. – peterSO Nov 21 '17 at 23:49
  • @peterSO - Do you have any links for reading/understanding why this is the case? – Hugh Nov 22 '17 at 00:05
  • 1
    @Hugh: [The Go Memory Model](https://golang.org/ref/mem). – peterSO Nov 22 '17 at 00:25
  • 1
    See my answer then... Also he ask a way to count how many times a function has been called, he didn't specify it should be thread safe or not. So @maerics answer is perfectly fine for me. – navossoc Nov 22 '17 at 01:27
  • In practice I think you'll still find most simple "counters" done with atomics, because pragmatically one usually wants lower contention over perfect ordering of operations. – JimB Nov 22 '17 at 13:28
  • @navossoc and peterSO this is still unclear to me, I can't tell any functional difference between the two to be honest. I've asked a question [here](https://stackoverflow.com/q/47445344/8930534) to try to clarify. – Hugh Nov 23 '17 at 01:16
  • @Hugh Chad Kunde told me this yesterday on slack group: "Each individual call is atomic, but there's no order guarantee across threads." Using this code (peterSO) you will enforce the correct order when you call the `fncCount.Count()`, so the number count will be **exact** at that given moment because you are forcing the execution to be serialized. However, if you don't care about getting the **exactly** numbers of calls when the goroutines are still running, you can use the `atomic` version (it may be off by a few). It is the same as @JimB has said above. – navossoc Nov 23 '17 at 05:45
5

Let me quote the atomic package documentation:

Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms. https://golang.org/pkg/sync/atomic/

Same code, but simpler and safe too.

package main

import (
    "fmt"
    "sync/atomic"
    "time"
)

var fncCount uint64

func fnc() {
    atomic.AddUint64(&fncCount, 1)
}

func main() {
    for i := 0; i < 42; i++ {
        go fnc()
    }
    // this is bad, because it won't wait for the goroutines finish
    time.Sleep(time.Second)

    fncCountFinal := atomic.LoadUint64(&fncCount)
    fmt.Println(fncCountFinal)
}

$ go run -race main.go

42

navossoc
  • 556
  • 1
  • 6
  • 16
  • You are conflating two different issues. There are no guarantees in the Go memory model that, in the absence of explicit synchronization, the result you see from an attomic.Load in one goroutine will correspond to the most recent atomic.Add in another goroutine. – peterSO Nov 22 '17 at 02:32
  • 1
    I see what you mean now, but he didn't say when he wants to get the calls count. So why overcomplicate it? – navossoc Nov 22 '17 at 03:02
4
var doThingCounter = 0
func DoThing() {
  // Do the thing...
  doThingCounter++
}
maerics
  • 151,642
  • 46
  • 269
  • 291