Hello Folks! Hope you are enjoying Golang Company blogs where we have already written about APi frameworks, data types and others.
Today, we will be covering one of the important concepts in Golang called Go Concurrency. This is where Go lang shines as it gives asynchronous ability to the language. Let’s start
First we will cover Goroutines.
Goroutines is basically a function that is getting scheduled by go scheduler. Go has a scheduler which is created at the run time. Important thing is goroutines do not run in parallel, they run concurrently. It may be confusing so let me explain it more.
Here, l write a small program
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
package main
import (
"fmt"
"time"
)
func main() {
fetchResource()
fetchResource()
fetchResource()
fetchResource()
fetchResource()
fetchResource()
fmt.Println("I am complete")
}
func fetchResource() string {
time.Sleep(time.Second * 2)
return "some string"
}
If you see, we are creating a function fetchResource() and calling it multiple times. When we run this program it will take 12 seconds or more to complete and Print “I am complete” on the console. This is because every function has a wait time of 2 seconds.
But, if we prepend “go” then all function calls become concurrent and we will immediately get an “I am complete” message on the console.
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
package main
import (
"fmt"
"time"
)
func main() {
go fetchResource() //async call
go fetchResource()
go fetchResource()
go fetchResource()
go fetchResource()
go fetchResource()
go fetchResource()
fmt.Println("I am complete")
}
func fetchResource() string {
time.Sleep(time.Second * 2)
return "some string"
}
The above example can also be attained through anonymous function.
Let’s understand by 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
27
28
29
30
31
32
33
34
35
package main
import (
"fmt"
"time"
)
func main() {
go func() { ///anonymous function
fetchResource() //async call
fetchResource()
fetchResource()
fetchResource()
fetchResource()
fetchResource()
fetchResource()
}()
fmt.Println("I am complete")
}
func fetchResource() string {
time.Sleep(time.Second * 2)
return "some string"
}
Here we just created one anonymous function and every function inside that because goroutines functions. Here go is prepended to func() which is anonymous as it does not have any name.
One step deeper to Goroutine: Channels
In the above example we saw that func fetchResource is returning a string but we are not using it anywhere.
If we try to write something like below it will not work. This is because function calls are happening outside and concurrently at the run time.
1
2
3
4
5
6
7
8
9
10
func main() {
go func() { ///anonymous function
result:= fetchResource()
fmt.Println(result)
}()
}
You can think of channels like a tunnel where we put one thing from one end and we receive the information on the other end. It is a way of communicating you may think of channels as a queue.
In General when we declare a channel we append “ch” to the variable so that others can understand that we are using channels.
How do we make channels?
We use make function/keyword to create a channel
Variable name := make(chan datatype)
We write into channel using <-
Let us understand with example:
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"
)
func main() {
resultch := make(chan string)
resultch <- "golang company" // adding data to channel
result := <-resultch // reading data from channel to variable
fmt.Println(result)
}
Comments are self explanatory. However when we run this program. We will get an ERROR!
I purposely did that so that when you will encounter later you understand the reason.
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
All goroutines are asleep why this happens. Now we have to understand the unbuffered and Buffered channels.
In the above example resultch := make(chan string)
is unbuffered channel. And the channel in go lang will always block if it is full. Therefore we are getting the deadlock error as there is no buffer size mentioned and hence it was blocked.
Now how to declare buffered channel, resultch := make(chan string, 10)
. Here we have a buffered channel with size 10. Let's run the program again with a buffered channel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import (
"fmt"
)
func main() {
resultch := make(chan string, 10) // buffered channel
resultch <- "golang company" // adding data to channel
result := <-resultch // reading data from channel to variable
fmt.Println(result)
}
It is running fine as the channel is not full. In our earlier code the moment we are adding string to our unbuffered channel it becomes full and therefore, getting deadlock error. So the channel should not be full as it will block.
So how we can make unbuffered channel work which is blocking us currently. Goroutine is the answer as it will schedule and will not block our channel. Let us understand with example:
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"
)
func main() {
resultch := make(chan string) // buffered channel
go func() {
resultch <- "golang company" // adding data to channel using go routine
}()
result := <-resultch // reading data from channel to variable
fmt.Println(result)
}
This will run without deadlock.
This is a complex thing in Golang. I tried to make it easier for you to understand with lots of code examples. I will dig deeper into channels in other parts. Keep reading. Have any issue or need to develop an application on Golang contact our golang developers.
So the above is more verbose to do. Range is cleaner but just showing you this can also be done.
Check the source code in the linked repository.
https://github.com/GolangCompany/go-concurrency-part01