Closure with Goroutine

I am a newbie in GoLang bringing a lot of baggage from other languages. With the code below things run correctly and count is 5. But if i use go Even(arr) count becomes 0. I thought Even(arr) will close over count and still be accessible while being processed in the goroutine (closure) and mutate count to 5.

What am i understanding wrongly?

var count = 0

func Even(arr []int) {
	for _, item := range arr {
		if item%2 == 0 {
			count++
		}
	}
}

func main() {
	arr := makeRange(1, 100000)
	Even(arr)
	fmt.Println(count)
}

Hi, @laiboonh,

Firstly, your Even function isn’t closing over count. count is a global variable that both main and Even have access to.

Secondly, though I understand that you know other languages and therefore I suspect you’re just trying to learn how to do concurrency in Go, here’s how I’d implement your program:

I think the right way to do this is to change Even so that instead of receiving a slice and updating the global count variable, you should change it to this:

func Even(arr []int) int {
	count := 0
	for _, item := range arr {
		if item%2 == 0 {
			count++
		}
	}
	return count
}

And then you use it from main like this:

func main() {
	arr := makeRange(1, 100000)
	count := Even(arr)
	fmt.Println(count)
}

Without this change, if you had another goroutine running that was also counting the number of even values with your Even function, they’d both be sharing that same count and both return the wrong value. Writing the function this new way makes it safe to use concurrently and sets us up for a slightly different example that I’d like to use to demonstrate the motivation for using a goroutine:

The go keyword starts a goroutine but doesn’t wait for it. You want to use goroutines when you want to run a function and then do other stuff without waiting (or while waiting) for that other function to finish. In your case, beause you’re just counting the number of even values and then only after you have the count, you want to print it out, you won’t get any benefit (in terms of performance or in terms of learning) in running Even in another goroutine. With the change I put above, though, you could instead run multiple Even functions in multiple goroutines:

import "sync"

func main() {
    arr := makeRange(1, 100000)

    wg := sync.WaitGroup{}
    wg.Add(2)

    firstHalf, secondHalf := 0, 0

    go func() {
        firstHalf = Even(arr[:50000])
        wg.Done()
    }()

    go func() {
        secondHalf = Even(arr[50000:])
        wg.Done()
    }()

    wg.Wait()

    fmt.Println(firstHalf + secondHalf)
}

This implementation executes Even in two goroutines and uses a sync.WaitGroup to, well, wait for the goroutines to finish counting the even numbers from both halfs of the slice.

2 Likes

Thank you for taking time to look at this.

I was deliberately trying to break this and to show that shared variables can cause a problem.

I wasn’t aware of this concept of WaitGroup. Thank you you introducing this. I was at first puzzled why the global variable count is always zero. Now i understand. It’s because main finished before the goroutines can even update the global variable. Hence it stays as 0.

I am able to prove to myself functionality does break with shared global variable together with the introduction of WaitGroup. count is an unstable value as i have come to expect :slight_smile:

func main() {
	arr := makeRange(1, 10000)
	firstHalf := arr[0 : len(arr)/2]
	secondHalf := arr[len(arr)/2:]

	wg := sync.WaitGroup{}
	wg.Add(2)

	go func() {
		Even(firstHalf)
		wg.Done()
	}()
	go func() {
		Even(secondHalf)
		wg.Done()
	}()

	wg.Wait()

	fmt.Println(count)

}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.