Contents
Concurrency in Golang
Goroutines
Goroutines allow functions to run concurrently and consume significantly less memory compared to traditional threads. Every Go program begins execution with a primary Goroutine, commonly referred to as the main Goroutine. If the main Goroutine exits, all other active Goroutines are terminated immediately.
Syntax:
func functionName() {
// statements
}
// To execute as a Goroutine
go functionName()
Example:
package main
import "fmt"
func showMessage(msg string) {
for i := 0; i < 3; i++ {
fmt.Println(msg)
}
}
func main() {
go showMessage("Hello, Concurrent World!") // Executes concurrently
showMessage("Hello from Main!")
}
Creating a Goroutine
To initiate a Goroutine, simply use the go
keyword as a prefix when calling a function or method.
Syntax:
func functionName() {
// statements
}
// Using `go` keyword to execute the function as a Goroutine
go functionName()
Example:
package main
import "fmt"
func printMessage(message string) {
for i := 0; i < 3; i++ {
fmt.Println(message)
}
}
func main() {
go printMessage("Welcome to Goroutines!") // Executes concurrently
printMessage("Running in Main!")
}
Output:
Running in Main!
Running in Main!
Running in Main!
Running Goroutines with Delay
Incorporating time.Sleep()
allows sufficient time for both the main and additional Goroutines to execute completely.
Example:
package main
import (
"fmt"
"time"
)
func printMessage(msg string) {
for i := 0; i < 3; i++ {
time.Sleep(300 * time.Millisecond)
fmt.Println(msg)
}
}
func main() {
go printMessage("Executing in Goroutine!")
printMessage("Executing in Main!")
}
Output:
Executing in Main!
Executing in Goroutine!
Executing in Goroutine!
Executing in Main!
Executing in Goroutine!
Executing in Main!
Anonymous Goroutines
You can also run anonymous functions as Goroutines by appending the go
keyword before the function.
Syntax
go func(parameters) {
// function body
}(arguments)
Example:
package main
import (
"fmt"
"time"
)
func main() {
go func(msg string) {
for i := 0; i < 3; i++ {
fmt.Println(msg)
time.Sleep(400 * time.Millisecond)
}
}("Anonymous Goroutine Execution!")
time.Sleep(1.5 * time.Second) // Wait for Goroutine to complete
fmt.Println("Main Goroutine Ends.")
}
Output:
Anonymous Goroutine Execution!
Anonymous Goroutine Execution!
Anonymous Goroutine Execution!
Main Goroutine Ends.
Select Statement
In Go, the select
statement allows you to wait for multiple channel operations to complete, such as sending or receiving values. Similar to a switch
statement, select
lets you proceed with the first available case, making it ideal for managing concurrent operations and handling asynchronous tasks effectively.
Example
Imagine you have two tasks that finish at different times. You can use select
to receive data from whichever task completes first.
package main
import (
"fmt"
"time"
)
func task1(ch chan string) {
time.Sleep(2 * time.Second)
ch <- "Task 1 finished"
}
func task2(ch chan string) {
time.Sleep(4 * time.Second)
ch <- "Task 2 finished"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go task1(ch1)
go task2(ch2)
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
Output:
Task 1 finished
In this example, “Task 1 finished” will be printed after 2 seconds, as task1
finishes before task2
. If task2
had completed first, the output would have been “Task 2 finished.”
Syntax
The select
statement in Go listens to multiple channel operations and proceeds with the first ready case.
select {
case value := <-channel1:
// Executes if channel1 is ready to send/receive
case channel2 <- value:
// Executes if channel2 is ready to send/receive
default:
// Executes if no other case is ready
}
Key Points:
- The
select
statement waits until at least one channel operation is ready. - If multiple channels are ready, one is selected at random.
- The
default
case is executed if no other case is ready, preventing the program from blocking.
Select Statement Variations
Basic Blocking Behavior: In this variation, we modify the example to remove the select
statement and see the blocking behavior when no channels are ready.
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
select {
case msg := <-ch:
fmt.Println(msg)
default:
fmt.Println("No channels are ready")
}
}
Output:
No channels are ready
Handling Multiple Cases: If multiple tasks are ready at the same time, select
chooses one case randomly. This can occur if tasks have nearly the same completion times.
Example:
package main
import (
"fmt"
"time"
)
func portal1(channel1 chan string) {
time.Sleep(3 * time.Second)
channel1 <- "Welcome from portal 1"
}
func portal2(channel2 chan string) {
time.Sleep(9 * time.Second)
channel2 <- "Welcome from portal 2"
}
func main() {
R1 := make(chan string)
R2 := make(chan string)
go portal1(R1)
go portal2(R2)
select {
case op1 := <-R1:
fmt.Println(op1)
case op2 := <-R2:
fmt.Println(op2)
}
}
Output:
Welcome from portal 1
Using Select with Default Case to Avoid Blocking: The default
case can be used to avoid blocking when no cases are ready. Here’s an example of modifying the structure to include the default
case.
package main
import (
"fmt"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:
fmt.Println("No tasks are ready yet")
}
}
Output:
No tasks are ready yet
Infinite Blocking without Cases: A select
statement with no cases will block indefinitely. This is commonly used when you need an infinite wait.
package main
func main() {
select {} // This blocks indefinitely because no cases are present
}
Output:
Welcome to Go Programming
Channels in Go Language
A channel in Go is a medium that enables communication between goroutines without using explicit locks. Channels facilitate the exchange of data between goroutines in a synchronized manner, and by default, they are bidirectional. This means the same channel can be used for both sending and receiving data. Below is a detailed explanation and examples of how channels work in Go.
Creating a Channel; In Go, you use the chan
keyword to create a channel. A channel can only transport data of a specific type, and you cannot use the same channel to transfer different data types.
Syntax:
var channelName chan Type
Example:
// Go program to demonstrate channel creation
package main
import "fmt"
func main() {
// Creating a channel using var
var myChannel chan string
fmt.Println("Channel value:", myChannel)
fmt.Printf("Channel type: %T\n", myChannel)
// Creating a channel using make()
anotherChannel := make(chan string)
fmt.Println("Another channel value:", anotherChannel)
fmt.Printf("Another channel type: %T\n", anotherChannel)
}
Output:
Channel value: <nil>
Channel type: chan string
Another channel value: 0xc00007c060
Another channel type: chan string
Sending and Receiving Data in a Channel
Channels in Go operate through two primary actions: sending and receiving, collectively referred to as communication. These operations use the <-
operator to indicate the direction of the data flow.
1. Sending Data: The send operation transfers data from one goroutine to another via a channel. For basic data types like integers, floats, and strings, sending is straightforward and safe. However, when working with pointers or references (like slices or maps), ensure that only one goroutine accesses them at a time.
myChannel <- value
2. Receiving Data: The receive operation fetches the data from a channel that was sent by another goroutin
var channelName chan Type
Example:
// Go program to demonstrate send and receive operations
package main
import "fmt"
func calculateSquare(ch chan int) {
num := <-ch
fmt.Println("Square of the number is:", num*num)
}
func main() {
fmt.Println("Main function starts")
// Creating a channel
ch := make(chan int)
go calculateSquare(ch)
ch <- 12
fmt.Println("Main function ends")
}
Output:
Main function starts
Square of the number is: 144
Main function ends
Closing a Channel: You can close a channel using the close()
function, which indicates that no more data will be sent to that channel.
Syntax:
close(channelName)
When iterating over a channel using a for range
loop, the receiver can determine if the channel is open or closed.
Syntax:
value, ok := <-channelName
If ok
is true
, the channel is open, and you can read data. If ok
is false
, the channel is closed.
Example:
// Go program to close a channel using for-range loop
package main
import "fmt"
func sendData(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go sendData(ch)
for value := range ch {
fmt.Println("Received value:", value)
}
fmt.Println("Channel closed")
}
Output:
Received value: 1
Received value: 2
Received value: 3
Received value: 4
Received value: 5
Channel closed
Key Points to Remember
Blocking Behavior:
Sending data blocks until another goroutine is ready to receive it.
Receiving data blocks until another goroutine sends it.
Nil Channels:
A zero-value channel is
nil
, and operations on it will block indefinitely.
Iterating Over Channels:
A
for range
loop can iterate over the values in a channel until it’s closed.
Example:
// Go program to iterate over channel using for-range
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hello"
ch <- "World"
ch <- "Go"
close(ch)
}()
for msg := range ch {
fmt.Println(msg)
}
}
Output:
Hello
World
Go
Channel Properties
1.Length of a Channel: Use len()
to find the number of elements currently in the channel.
Example:
// Go program to find channel length
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 10
ch <- 20
fmt.Println("Channel length:", len(ch))
}
Output:
Channel length: 2
2. Capacity of a Channel: Use cap()
to find the total capacity of the channel.
Example:
// Go program to find channel capacity
package main
import "fmt"
func main() {
ch := make(chan float64, 4)
ch <- 1.1
ch <- 2.2
fmt.Println("Channel capacity:", cap(ch))
}
Output:
Channel capacity: 4
In Golang, channels act as a communication mechanism between concurrently running goroutines, allowing them to transmit and receive data. By default, channels in Go are bidirectional, meaning they support both sending and receiving operations. However, it’s possible to create unidirectional channels, which can either exclusively send or receive data. These unidirectional channels can be constructed using the make()
function as demonstrated below:
// For receiving data only
c1 := make(<-chan bool)
// For sending data only
c2 := make(chan<- bool)
Example:
// Go program to demonstrate the concept
// of unidirectional channels
package main
import "fmt"
func main() {
// Channel restricted to receiving data
recvOnly := make(<-chan int)
// Channel restricted to sending data
sendOnly := make(chan<- int)
// Display the types of the channels
fmt.Printf("%T", recvOnly)
fmt.Printf("\n%T", sendOnly)
}
Output:
<-chan int
chan<- int
Converting Bidirectional Channels into Unidirectional Channels
In Go, you can convert a bidirectional channel into a unidirectional channel, meaning you can restrict it to either sending or receiving data. However, the reverse conversion (from unidirectional back to bidirectional) is not possible. This concept is illustrated in the following example:
Example:
// Go program to illustrate conversion
// of a bidirectional channel into a
// unidirectional channel
package main
import "fmt"
// Function to send data through a send-only channel
func sendData(channel chan<- string) {
channel <- "Hello from Golang"
}
func main() {
// Creating a bidirectional channel
bidiChannel := make(chan string)
// Passing the bidirectional channel to a function,
// which restricts it to a send-only channel
go sendData(bidiChannel)
// Receiving data from the channel
fmt.Println(<-bidiChannel)
}
Output:
Hello from Golang