Concurrency VS Synchronization
Synchronous code
When code executes line by line in order, one thing at a time is called synchronous. It is simple, but sometimes might not be very efficient.
Concurrency
func main() {
// block 1
x := 10
x++
fmt.Printf("x = %d", x)
// block 2
y := 20
y--
fmt.Printf("y = %d", y)
}
Check the code blocks in the above example. As you can see, the code lines in block 1 should execute in order. The code lines in block 2 should also execute line by line. But blocks 1 and 2 have no dependency on each other. So, we can run them separately on two CPU cores. This is called running in Parallel. It increases efficiency.
In Golang, it is easy to write concurrent code.
Go routine
func main() {
// some code
go funcName()
// rest of the code
}
In the above example, you can see go funcName()
. It tells that the funcName()
function runs in parallel with the rest of the code in the main function.
So all you need to do is use the go
keyword to instruct it to run in concurrent mode. We have a special name here for concurrent functions: go routine. In the above example, funcName()
is a go routine.
We can't expect return values from a go routine like we get from a regular function.
Now, you might wonder what the use of these go routines is if they can't even return a value. That is why we have channels.
Channels
Channels are used to communicate between goroutines. They are a type-safe and thread-safe queue.
One or more goroutines can put data into a channel, and one or more other goroutines read that data from the channel.
Syntax to make a channel,
ch := make(chan int)
Above, initialize a channel of type int.
You can put data into channels using the syntax below.
ch <- 10
The <-
is called the channel operator. The arrowhead shows the data flow direction.
Use the below syntax to receive data from a channel.
val := <- ch
It removes the value from the channel and saves it to the val
variable.
Both sending and receiving data operations are blocking operations. If a value is sent to a channel but there is no other goroutine to receive the data, the code will stop and wait. The same happens during the reading. When a goroutine is expecting data from a channel, but no one is writing data to it, then the code is blocked there.
A token is a unary value. Most of the time, empty structs are used as tokens. What is passed through the channel is not a concern in this scenario. <-ch
syntax is used to block and wait until something is sent to a channel.
Buffered Channels
When making a channel, we can make it buffered by providing a buffer length as the second argument.
ch := make(chan int, 100)
When the buffer is full, sending to the channel gets blocked. Likewise, when the buffer is empty, the receiving from the channel is blocked.
Closing channels
Channels should always be closed from the sending side. It says that this channel is closed, there's nothing more to read from it. The syntax is given below.
ch := make(chan int)
// code lines ...
close(ch)
Therefore, on the reading side, we have to check whether a channel is closed.
v, ok := <-ch
In the above example, the ok
is a boolean variable. Its value is true
if the channel is open. Else, ok
is false
.
If you send a value to a closed channel, that go routine will panic.
Closing a channel is not necessary. Unused open channels are garbage collected.
Range
The range
can be used on channels as well. The example below receives values over the channel until it is closed. When the channel is closed, the loop exists.
for item := range ch {
// item is the next value received from the ch
}
Select
When there is a single goroutine listening to multiple channels, we might need to process data in order per channel. In this case, we can use the select
statement to switch between channels.
select {
case i, ok := <- chInst:
if !ok {
return
}
fmt.Print(i)
case s, ok := chString:
if !ok {
return
}
fmt.Print(i)
}
Read-only channels
When you want to make a channel read-only, you should cast it from chan
to <-chan
type.
func readCh(ch <-chan int) {
// ch is a read-only channel in this function
}
Write-only channels
To make a channel write-only, you should cast it from chan
to chan<-
type.
func readCh(ch chan<- int) {
// ch is a write-only channel in this function
}
Read-only and write-only channels concept is useful in identifying which functions use the channel for reading and which use it for writing.
Few points to remember:
- A send to a nil channel blocks the code forever
- A receive from a nil channel blocks forever
- A send to a closed channel panics
- A receive from a closed channel returns the zero value
Top comments (0)