- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
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.
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.
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.
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
}
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.
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.We can't expect return values from a go routine like we get from a regular function.
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.
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.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.
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.
Closing a channel is not necessary. Unused open channels are garbage collected.If you send a value to a closed channel, that go routine will panic.
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
}
Few points to remember:Read-only and write-only channels concept is useful in identifying which functions use the channel for reading and which use it for writing.
- 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