Concurrency is when two or more tasks make progress simultaneously. It could be very tedious to get it right in most of the programming languages. However, Go has a rich support to concurrency and makes the task very easy.
Goroutines:
Goroutines are functions or methods that can run concurrently with other methods. They are light-weight threads. The cost of creating a goroutine is very small compared to that of an actual thread. Hence a GO application commonly has thousands of goroutines running concurrently.
- Goroutines are managed by GO runtimes
go exroutine(a,b)
starts a new goroutine that runsexroutine(a,b)
- Here, the evaluation of exroutine,a,b happens in the current goroutine. The execution happens in the new goroutine.
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 10; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("is fun")
say("learning go")
}
Channels:
- Channels facilitate sending and receiving values. It is achieved using the channel operator ‘<-‘
- Sending a to channel ch:
ch <- a
- Receive from ch and assign it to a:
a := <-ch
(Data flows in the direction of the arrow) - Channels must be created before using it:
ch := make(chan int)
- Send and Receive blocks implicitly until the other side is ready and hence is synchronized.
package main
import "fmt"
func sum(s []int, c chan int){
sum := 0
for _,v := range s{
sum += v
}
c <- sum
}
func main(){
lst := []int{200, 100, 900, 30, 40, 70, 400, 450}
c := make(chan int)
go sum(lst[:len(lst)/2],c)
go sum(lst[len(lst)/2:],c)
x,y := <-c, <-c
fmt.Println(x,y,x+y)
}
- Channels can also be bufferred. The buffer length should be provided as the second argument to make:
ch := make(chan int, 100)
- A channel can be closed to indicate that no more values can be sent.
close(ch)
- Only the sender should close a channel (not the receiver).
- Receivers can check if a channel has been closed using the second parameter:
a, ok := <-ch
ok is False if the channel is closed. - range can be used to keep receiving values until the channel is closed.
for i := range ch {
fmt.Println(i)
}
- Closing a channel is not mandatory. It is only required to send a message to the receiver that no more values are being sent through the channel. It can also be useful to terminate a range loop.
Select
Select lets a goroutine wait on multiple communication operations. It chooses the case that is ready. If multiple are ready, it randomly makes a choice. Default case is chosen if no other case is ready.
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
default:
fmt.Println(" .")
}
Mutex:
Mutex or mutual exclusion ensures that only one goroutine can access a variable at a given time to avoid conflicts. Mutex is provided by the sync library which has two methods: Lock and Unlock.
- A block of code can be executed in mutual exclusion by enclosing it in a call to lock.
- Defer ensures the mutex is unlocked when required.
package main
import (
"fmt"
"sync"
"time"
)
type mutexUsage struct {
v map[string]int
mx sync.Mutex
}
func (m *mutexUsage) Incr(key string) {
m.mx.Lock()
m.v[key]++
m.mx.Unlock()
}
func (m *mutexUsage) Value(key string) int {
m.mx.Lock()
defer m.mx.Unlock()
return m.v[key]
}
func main() {
m := mutexUsage{v: make(map[string]int)}
for i := 0; i < 100; i++ {
go m.Incr("somekey")
}
time.Sleep(time.Second)
fmt.Println(m.Value("somekey"))
}
One thought