In Go, concurrency is achieved through goroutines. Goroutines are lightweight threads managed by the Go runtime, allowing for concurrent execution of functions. Unlike traditional threads, which are heavy and resource-intensive, goroutines are more efficient and scalable.
Goroutines are a fundamental feature of Go’s concurrency model. They enable concurrent execution of functions or methods within a Go program. Goroutines are lightweight threads managed by the Go runtime, allowing for efficient concurrency without the overhead typically associated with traditional threads.
Here are some key aspects of Goroutines:
1. Concurrent Execution: Goroutines allow multiple functions or methods to execute concurrently within a Go program. They enable parallelism and can perform tasks simultaneously.
2. Lightweight: Goroutines are lightweight compared to traditional threads. The Go runtime multiplexes many goroutines onto a smaller number of operating system threads, allowing for efficient usage of system resources.
3. Syntax: Goroutines are created using the `go` keyword followed by a function or method call. This initiates the concurrent execution of the specified function or method.
4. Communication: Goroutines communicate with each other and synchronize their execution through channels, which are a typed conduit for data. Channels facilitate safe communication and data sharing between Goroutines.
5. Non-blocking: Goroutines are non-blocking, meaning that when a Goroutine is created using the `go` keyword, the program continues execution without waiting for the Goroutine to complete. This allows for asynchronous execution of tasks.
6. Scalability: Goroutines are highly scalable due to their lightweight nature. It’s common to have thousands or even millions of Goroutines running concurrently within a single Go program without significant performance degradation.
Goroutines are a powerful mechanism for achieving concurrency and parallelism in Go programs, enabling efficient utilization of modern multi-core processors and facilitating the development of highly responsive and scalable applications.
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello from Goroutine!")
}
func main() {
// Create a new goroutine
go sayHello()
// Wait for a second to allow the goroutine to execute
time.Sleep(time.Second)
fmt.Println("Main function")
}
Main function
Hello from Goroutine!
main
function starts by creating a new goroutine using the go
keyword to execute the sayHello
function concurrently.main
function waits for one second using time.Sleep(time.Second)
.main
function resumes execution and prints “Main function”.Goroutines are created using the go
keyword followed by the function call. The function or method is executed concurrently in its own goroutine.
package main
import (
"fmt"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
}
func main() {
// Create goroutine for printNumbers function
go printNumbers()
fmt.Println("Main function")
}
Main function
1
2
3
4
5
package main
: This line declares that the current file belongs to the main
package, which is required for creating executable Go programs.
import "fmt"
: This imports the “fmt” package, which is Go’s standard library package used for formatting and printing output.
func printNumbers() { ... }
: This function definition defines a function named printNumbers
. This function prints numbers from 1 to 5 using a for loop and fmt.Println
to print each number on a new line.
func main() { ... }
: This is the entry point of the program. The main
function is executed when the program starts.
go printNumbers()
: This line creates a new goroutine to execute the printNumbers
function concurrently with the main
function. Goroutines are lightweight threads managed by the Go runtime, allowing concurrent execution of functions.
fmt.Println("Main function")
: This line prints “Main function” to the standard output. It’s executed immediately after the goroutine creation statement.
Sometimes, we need to wait for goroutines to complete their execution before proceeding further. We can achieve this using synchronization techniques such as channels or sync packages.
package main
import (
"fmt"
"sync"
)
func printNumbers(wg *sync.WaitGroup) {
defer wg.Done() // Notify WaitGroup when done
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
}
func main() {
var wg sync.WaitGroup
// Add 1 to WaitGroup
wg.Add(1)
// Create goroutine for printNumbers function
go printNumbers(&wg)
// Wait for all goroutines to finish
wg.Wait()
fmt.Println("Main function")
}
In this example, we use sync.WaitGroup
to wait for the goroutine to complete its execution.
1
2
3
4
5
Main function
Goroutines start executing concurrently as soon as they are created using the go
keyword.
package main
import (
"fmt"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
}
func main() {
// Create goroutine for printNumbers function
go printNumbers()
fmt.Println("Main function")
}
The output of this Go program may vary due to the concurrent execution of the printNumbers
goroutine. Here’s what might happen:
main
function starts executing.printNumbers
function and immediately proceeds to the next line.printNumbers
goroutine starts executing concurrently.main
function might exit before the printNumbers
goroutine finishes execution, causing the program to terminate without waiting for printNumbers
to complete.So, the output could be:
Main function
1
2
3
4
5
Or, if the main
function exits before the goroutine completes, you might only see:
Main function
Or, due to the concurrent nature of goroutines, the output might vary each time you run the program.
Goroutines start executing concurrently as soon as they are created using the go
keyword.
package main
import (
"fmt"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
}
func main() {
go printNumbers()
fmt.Println("Main function")
}
The output of this Go program can vary due to the concurrent execution of the printNumbers
goroutine. Let’s analyze what happens:
main
function starts executing.printNumbers
function and immediately proceeds to the next line.printNumbers
goroutine starts executing concurrently.Now, there are two possibilities for the order of execution due to the concurrent nature of goroutines:
Main function
printed first, then numbers
Main function
1
2
3
4
5
In this scenario, the Main function
is printed first, followed by the numbers printed by the printNumbers
goroutine.
Main function
1
2
3
4
5
Main function
In this scenario, the numbers printed by the printNumbers
goroutine are printed first, and then “Main function” is printed from the main
goroutine.
The go
keyword in go printNumbers()
tells Go to execute printNumbers
concurrently in a separate goroutine. This means that printNumbers
will start executing concurrently with the main
function, which allows both printNumbers
and main
to run independently and simultaneously. The exact order of execution between the two is nondeterministic and depends on factors like the scheduler and available resources.
So, the output can vary depending on how the scheduler decides to interleave the execution of goroutines. It might be “Main function” followed by numbers, or numbers followed by “Main function”.
Channels are the primary way goroutines communicate with each other. They allow sending and receiving values between goroutines.
package main
import "fmt"
// sender function sends a value to the channel
func sender(ch chan<- int) {
ch <- 42 // Sending value to channel
}
// receiver function receives a value from the channel
func receiver(ch <-chan int) {
fmt.Println("Received:", <-ch) // Receiving value from channel
}
func main() {
ch := make(chan int) // Creating an unbuffered channel
go sender(ch) // Launching sender goroutine
go receiver(ch) // Launching receiver goroutine
fmt.Scanln() // Waiting for user input to exit
}
sender
function takes a channel ch
that can only be used for sending (chan<- int
). It sends the integer value 42
into the channel.receiver
function takes a channel ch
that can only be used for receiving (<-chan int
). It receives a value from the channel and prints it.main
function, an unbuffered channel ch
of type int
is created using make(chan int)
. This means that the sender and receiver must be synchronized; a send operation on an unbuffered channel blocks until another goroutine is ready to receive the value.sender
function, which sends the value 42
into the channel ch
.receiver
function, which waits to receive a value from the channel ch
.fmt.Scanln()
is used to keep the main goroutine alive until the user presses Enter. This is done to allow the other goroutines to complete their tasks before the program exits.Since both sender and receiver goroutines are running concurrently, their execution order is not guaranteed. However, due to the nature of the Go scheduler, typically one of the goroutines will finish before the other, so you’ll usually see output like this:
Received: 42
This output indicates that the receiver
function received the value 42
from the channel. However, depending on the scheduler, it’s possible for the output order to vary between runs.
Goroutine pools are a collection of pre-initialized goroutines ready to execute tasks. They help in managing resources efficiently.
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("Worker", id, "processing job", j)
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// Create goroutines
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Send jobs
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// Collect results
for a := 1; a <= numJobs; a++ {
<-results
}
}
worker
function simulates a worker that processes jobs. It takes three parameters:
id
: Worker ID for identification.jobs
: A channel from which jobs are received.results
: A channel where processed results are sent.In the main
function:
jobs
and results
are created. The size of these channels is set to numJobs
to ensure they can hold all jobs and results without blocking.for w := 1; w <= 3; w++
), each with a unique ID, and pass them the jobs
and results
channels.for j := 1; j <= numJobs; j++
) is used to send five jobs to the jobs
channel. After sending all jobs, the jobs
channel is closed using close(jobs)
to signal that no more jobs will be sent.for a := 1; a <= numJobs; a++
) is used to receive all results from the results
channel. Since the results
channel is buffered and the same size as the number of jobs, this loop ensures that all results are received.
Worker 2 processing job 1
Worker 1 processing job 2
Worker 3 processing job 3
Worker 1 processing job 4
Worker 3 processing job 5
Goroutines in Go provide a powerful mechanism for concurrent programming. They are lightweight, easy to create, and facilitate communication through channels. By utilizing goroutines effectively, you can build highly concurrent and scalable applications in Go. Happy coding !❤️