Tutorial : Go Concurrency - Controlling Channels,Mutexes and Atomic Values (Part 4)

Go Concurrency: Controlling Channels, Mutexes and Atomic Values's picture

Thanks for reading the previous blog about concurrency. So, I am very excited to take you through the final blog on mutexes and atomic values. You might have got the fair idea that concurrency in go is a different programming style. And it is the most important concept you can say concurrency is the bread and butter of Go programming as it allows distributed programming.

State concept:

Concurrency is a different style of programming. It is hard to debug. The reason is because of the concept state. Let me explain with an example.

So take a basic example

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package main import "fmt" type State struct { count int } func main() { state := State{} for i := 0; i < 10; i++ { state.count = i } fmt.Println(state) }

And let’s create a file main_test.go and cut the code written in the function to main.go

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package main import ( "fmt" "testing" ) func TestState(t *testing.T) { state := State{} for i := 0; i < 10; i++ { state.count = i } fmt.Printf("%+v\n", state) }

Out put is
go test ./... -v
=== RUN TestState
{9}
--- PASS: TestState (0.00s)
PASS
ok

So our test is running fine. Let’s go ahead and make this go routine.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func TestState(t *testing.T) { state := State{} wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { state.count = i+1 wg.Done() }(i) } wg.Wait() fmt.Printf("%+v\n", state) }

We get different outputs depending on the speed of the CPU. Here are some outputs which I received

ok github.com/anurag4way/go-concurrency 0.033s
PS C:\Users\4 way technologies\Desktop\go-workspace\go-concurrency> go test ./... -v -count=1
=== RUN TestState
{count:10}
--- PASS: TestState (0.00s)
PASS
ok github.com/anurag4way/go-concurrency 0.039s
PS C:\Users\4 way technologies\Desktop\go-workspace\go-concurrency> go test ./... -v -count=1
=== RUN TestState
PASS
ok github.com/anurag4way/go-concurrency 0.034s
PS C:\Users\4 way technologies\Desktop\go-workspace\go-concurrency> go test ./... -v -count=1
=== RUN TestState
{count:6}
--- PASS: TestState (0.00s)
PASS
ok github.com/anurag4way/go-concurrency 0.030s
PS C:\Users\4 way technologies\Desktop\go-workspace\go-concurrency> go test ./... -v -count=1
=== RUN TestState
{count:7}
--- PASS: TestState (0.00s)
PASS
ok github.com/anurag4way/go-concurrency 0.036s
PS C:\Users\4 way technologies\Desktop\go-workspace\go-concurrency>

This is the problem everything seems to be working and we will see a different output. This is because of concurrency. We call this a race condition this happens when multiple programs are trying to change the value of one variable.

To avoid this problem we use mutexes.

What are mutexes:

You can think of mutexes as a flag that means there will be one flag and whosoever have that flag will have access to the variable rest will wait.
main.go file

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package main import ( "sync" ) type State struct { mu sync.Mutex count int } func (s *State) setState(i int) { s.mu.Lock() defer s.mu.Unlock() s.count = i } func main() { // state := &State{} // for i := 0; i < 10; i++ { // state.count = i // } // fmt.Println(state) }

Here, we have declared mu variable of the sync mutex package. The variable which has to be used multiple for concurrency is getting locker by mu.Lock() and deferred so that it can unlock after execution this is done to avoid race conditions.
Main_test.go file code

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package main import ( "fmt" "sync" "testing" ) func TestState(t *testing.T) { state := &State{} wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { state.setState(i+1) wg.Done() }(i) } wg.Wait() fmt.Printf("%+v\n", state) }

Now we have covered Mutexes, this should be done if we are using maps as maps are not concurrent safe, and with Mutex, we can lock the map to be updated and have better control of our program.

Let’s come to the Atomic values.

What are Atomic values?

Atomic values are the way to increment and decrement the value in an atomic way. The atomic way means you are able to synchronize like Mutexes. Atomic values can be used for all types of variables but should be used for counters or boolean datatypes. Let 's see in practice

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package main import ( "sync" "sync/atomic" ) type State struct { count int32 } func (s *State) setState(i int32) { atomic.AddInt32(&s.count, int32(i)) } func main() { }

In general, we make the program race-free using mutexes just here we showed the way to use Atomic values so that we know their way of writing controlled go routines programs.

Check the source code in the linked repository.

https://github.com/GolangCompany/go-concurrency-part04

Conclusion:

This is the final part of our blog cum tutorial. We tried to cover most of the topics like channels, wait for groups, goroutines which are critical for go programming. Hope you have enjoyed our blogs feel free to reach out to us. If you have questions or want to hire our go developers for your project.


Build Your Golang Team