simotunes
simotunes

Reputation: 187

How to create global counter in highly concurrent system

I'm creating global counter, which can be shared between goroutines. Referring to this question, following code may satisfy my needs.

However if there ware lots of concurrent requests, could it happen that the same number is assigned to more than two goroutines? If so how can I avoid this?

This question is different from the link I pasted, as what I want to know about is how I can avoid duplication using channel counter. if the only possible solution is other implementation like sync.Mutex or atomic, I'll use it. however, according to the link (again), channel seems to be the best option. Any comment or answer really helpful. thanks in advance. I'm new to multithread coding and also go, might be silly question. sorry for that.

package main

import (
    "fmt"
    "time"
)

var counter int
var counter_chan chan int

func main() {
    counter_chan = make(chan int, 100)

    counter = 0

    go func() {
        for {
            select {
            case chanc := <-counter_chan:
                counter += chanc
                fmt.Printf("%d \n", counter)
            }
        }
    }()

    for i := 0; i < 10; i++ {
        go AddCounter(counter_chan)
    }

    time.Sleep(time.Second)
    fmt.Printf("Total Count is ... %d \n", GetCount())

}

func AddCounter(ch chan int) {
    ch <- 1
}

func GetCount() int {
    return counter
}

func ResetCount() {
    if counter > 8190 {
        counter = 0
    }
}

-- Edit 05/14 2018

Assume following code is thread-safe for getting and resetting value. Am I right?

package main

import (
    "fmt"
    "time"
)

var counter int
var addCounterChan chan int
var readCounterChan chan int

func main() {
    addCounterChan = make(chan int, 100)
    readCounterChan = make(chan int, 100)

    counter = 0

    go func() {
        for {
            select {
            case val := <-addCounterChan:
                counter += val
                if counter > 5 {
                    counter = 0
                }
                readCounterChan <- counter
                fmt.Printf("%d \n", counter)
            }
        }
    }()

    for i := 0; i < 10; i++ {
        go AddCounter(addCounterChan)
    }

    time.Sleep(time.Second)

    for i := 0; i < 10; i++ {
        fmt.Printf("Total Count #%d is ... %d \n", (i + 1), GetCount(readCounterChan))
    }

}

// Following two functions will be implemented in another package in real case.
func AddCounter(ch chan int) {
    ch <- 1
}

func GetCount(ch chan int) int {
    r := <-ch
    return r
}

Upvotes: 5

Views: 6867

Answers (3)

blackgreen
blackgreen

Reputation: 45081

Go 1.19

The sync/atomic package now includes atomic types, such as atomic.Int32, which you can use to manage a value that can only be accessed atomically.

This basically accomplishes the same thing as having a custom struct with a mutex, or using top-level atomic functions to read and write a "naked" numerical type. Instead of rolling your own, you can simply rely on the standard library.

A simple example:

package main

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

// zero value is 0
var counter = atomic.Int32{}

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(100)
    for i := 0; i < 100; i++ {
        go func() {
            counter.Add(1)
            wg.Done()
        }()
    }

    wg.Wait()
    fmt.Println(counter.Load())
}

Playground: https://go.dev/play/p/76xM3xXTAM5?v=gotip

Upvotes: 1

Jonathan Hall
Jonathan Hall

Reputation: 79734

The direct answer to your question is: The code you've pasted updates the counter safely, but doesn't read or reset it safely.

Contrary to the accepted answer in the question you linked to, however, the easiest, most efficient way to implement a shared counter is with the atomic package. It can be used to atomically increment several common types. Example:

var globalCounter *int32 = new(int32)

// .. later in your code
currentCount := atomic.AddInt32(globalCounter, 1)

Upvotes: 15

Thundercat
Thundercat

Reputation: 121119

Use a sync.Mutex to create a counter with add, get and reset operations as shown in the question.

type counter struct {
    mu sync.Mutex
    n  int
}

func (c *counter) Add() {
    c.mu.Lock()
    c.n++
    c.mu.Unlock()
}

func (c *counter) Get() int {
    c.mu.Lock()
    n := c.n
    c.mu.Unlock()
    return n
}

func (c *counter) Reset() {
    c.mu.Lock()
    if c.n > 8190 {
        c.n = 0
    }
    c.mu.Unlock()
}

If the reset function is not needed, then use the sync/atomic.

type counter struct {
    n int32
}

func (c *counter) Add() {
    atomic.AddInt32(&c.n, 1)
}

func (c *counter) Get() int {
    return int(atomic.LoadInt32(&c.n))
}

Upvotes: 7

Related Questions