Go language (also known as Golang) is a programming language developed by Google, renowned in the programming field for its simplicity, efficiency, and built-in support for concurrent programming.
In Go language, goroutine is a powerful feature for the creation and management of lightweight threads.
This article will introduce the concept of goroutine, its applications, and provide specific examples to demonstrate its usage to those who are unfamiliar with Go language.
Goroutine is a lightweight thread used for concurrent execution in Go language. Compared to traditional threads, goroutines are much cheaper to create and destroy, allowing the easy creation of numerous goroutines within the same program without degrading performance. Goroutines communicate with each other through channels, making concurrent programming straightforward and safe.
As the saying goes, “Give roses to the beloved and swords to the warriors.” Given the remarkable capabilities of Goroutine, the field of concurrent programming is its stage to shine.
Goroutine makes its presence felt in the following application scenarios:
Let’s explain through some specific examples.
As a warm-up, let’s start with some simple “Hello World” level examples to get familiar with the usage of Goroutine.
Source code is as follows:
import (
"fmt"
"sync"
)
func printNumbers(wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= 5; i++ {
fmt.Printf("%d ", i)
}
}
func printLetters(wg *sync.WaitGroup) {
defer wg.Done()
for char := 'a'; char <= 'e'; char++ {
fmt.Printf("%c ", char)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go printNumbers(&wg)
go printLetters(&wg)
wg.Wait()
}
Save the above source code in a .go file, such as 1.go
, and then execute the command line go run 1.go
to see the following output:
In this example, we created two goroutines using the go
keyword, one for printing numbers and the other for printing letters. The call to the sync.WaitGroup
function waits for these two goroutines to complete. This is one of the most commonly used synchronization mechanisms in Go's concurrent programming.
Source code is as follows:
import (
"fmt"
"io/ioutil"
"net/http"
"sync"
)
func fetch(url string, wg *sync.WaitGroup) {
defer wg.Done()
response, err := http.Get(url)
if err != nil {
fmt.Printf("Error fetching %s: %v\n", url, err)
return
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
fmt.Printf("Error reading response body from %s: %v\n", url, err)
return
}
fmt.Printf("Length of %s: %d\n", url, len(body))
}
func main() {
var wg sync.WaitGroup
urls := []string{"https://www.baidu.com", "https://cloud.tencent.com/", "https://www.qq.com/"}
for _, url := range urls {
wg.Add(1)
go fetch(url, &wg)
}
wg.Wait()
}
After executing the above source code, the console prints the length of the homepage HTML files for three websites (Baidu, QQ, and Tencent Cloud Community).
This example demonstrates how to use goroutines to concurrently download multiple web pages. Each webpage is downloaded in a separate goroutine, reducing the time required to complete the download tasks.
From the source code, it can be seen that if we were to implement this requirement using a traditional programming language like Java, we would need to use Java’s Thread class and some cumbersome synchronization mechanism codes. However, using Go language, the task of starting goroutines and reading HTML page data based on URLs can be easily accomplished with the concise line go fetch(url, &wg)
, greatly simplifying the code.
The producer-consumer model is one of the essential concepts in operating system courses. We use the func
keyword to define two functions, one for the producer and one for the consumer. The producer function uses the time.Sleep
method to produce a sequence of positive integers 1,2,3,4,5 every 0.5 seconds. Correspondingly, the consumer function consumes a positive integer every 0.5 seconds.
import (
"fmt"
"sync"
"time"
)
func producer(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= 5; i++ {
ch <- i
fmt.Printf("Produced: %d\n", i)
time.Sleep(time.Millisecond * 500) // Simulate a delay in the production process
}
close(ch)
}
func consumer(ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for num := range ch {
fmt.Printf("Consumed: %d\n", num)
time.Sleep(time.Millisecond * 200) // Simulate a delay in the consumption process
}
}
func main() {
var wg sync.WaitGroup
ch := make(chan int, 3) // Create a buffered channel with a capacity of 3
wg.Add(2)
go producer(ch, &wg)
go consumer(ch, &wg)
wg.Wait()
}
Executing the above Go source code outputs a sequence of produced integers followed by consumption, and so on.
This example demonstrates how to use goroutines and channels to implement the producer-consumer model. The producer sends data to the channel, and the consumer receives and processes data from the channel, achieving concurrent production and consumption through goroutines.
We used a buffered channel with a capacity of 3. Buffered channels allow writing to the channel even when it is not fully read, which is very useful when the producer and consumer operate at different speeds.
In the producer function producer
, ch
is a write-only integer channel, and wg
is a pointer of type sync.WaitGroup
, used to wait for all goroutines to complete. In this function, the producer sends the integer i
to the channel with ch <- i
, indicating a product has been produced. Then, it prints the information of the produced product with fmt.Printf
and simulates the production process delay with time.Sleep
. Finally, it closes the channel with close(ch)
, indicating that the producer will no longer send data to the channel.
Looking at the consumer function consumer
,
ch
is a read-only integer channel, and wg
is a pointer of type sync.WaitGroup
. In this function, the consumer receives an integer from the channel with num := <-ch
, indicating a product has been consumed. Then, it prints the information of the consumed product with fmt.Printf
and simulates the consumption process delay with time.Sleep
. This loop continues until the channel is closed, at which point range ch
will exit.
Through the three examples introduced in this article, we can see the strength of goroutines. With the combination of goroutines and channels, as well as the use of sync.WaitGroup
, Go language offers a powerful and concise mechanism for concurrent
To unsubscribe from this group and stop receiving emails from it, send an email to forum+un...@jsoftware.com.