SoFunction
Updated on 2025-03-05

Super practical Golang channel guide for easy implementation of concurrent programming

1. What is a Golang channel

The channel in Golang is an efficient, secure and flexible concurrency mechanism used to synchronize and pass data in a concurrent environment. The channel provides a thread-safe queue that allows only one goroutine to read and another goroutine to write. In this way, channels can effectively solve common problems such as race conditions and locking problems in concurrent programming.

There are two types of channels: buffered and unbuffered. When creating a channel, you can specify the capacity of the channel, that is, the size of the channel buffer, and if not specified, the default is to an unbuffered channel.

2. Basic syntax of Golang channel

The basic syntax of Golang channels is very simple, use the make function to create a channel:

 ch := make(chan int)

This line of code creates a channel named ch with the data type of the channel being int. The read and write operations of a channel can use the arrow symbol <-, <- to indicate reading data from the channel, and -> to indicate writing data into the channel. For example:

 ch := make(chan int)
 ch &lt;- 1 // Write data 1 to the channel x := &lt;- ch // Read data from the channel and assign value to the variable x

3. Buffering mechanism of Golang channel

In Golang, channels also support buffering mechanisms. The buffer of the channel can store a certain amount of data. When the buffer is full, writing data to the channel will block. When the channel buffer is empty, reading data from the channel will block. Using a buffering mechanism can increase program flexibility and concurrency performance.

Channels with buffer size 0 are called unbuffered channels. Both send and receive operations on unbuffered channels are blocked, so a receiver must be ready to receive to perform the send operation and vice versa. This mechanism ensures the synchronization of the channel, that is, before and after the channel operation, the sender and receiver are blocked until the other party is ready.

3.1 Buffered channel

The creation of buffered channels is:

 ch := make(chan int, 3)

This line of code creates a channel named ch, the channel's data type is int, and the channel buffer size is 3. When writing data to a buffered channel, if the buffer is not full, the write operation will be successful and the program will continue to execute. If the buffer is full, the write operation will block until an free buffer is available.

When reading data from a buffered channel, if the buffer is not empty, the read operation will be successful and the program will continue to execute. If the buffer is empty, the read operation will block until data is readable.

3.2 Unbuffered channel

The creation of unbuffered channels is:

 ch := make(chan int)

This line of code creates a channel named ch, the channel's data type is int, and the channel buffer size is 0. Both send and receive operations on unbuffered channels are blocked, so a receiver must be ready to receive to perform the send operation and vice versa.

4. Timeout and timer for Golang channel

In concurrent programming, it is often necessary to time out and time the channel. The time package is provided in Golang to implement timeouts and timers.

4.1 Timeout mechanism

In Golang, you can use select statements and functions to implement channel timeout operations. For example:

 select {
     case data := <-ch:
         (data)
     case <-():
         ("timeout")
 }

In this code, the select statement listens to the two channels, ch and () channels. If there is data in ch, it will read and output the data; if there is still no data after waiting for 1 second, timeout will be timed out and timeout will be output.

4.2 Timer mechanism

The time package is provided in Golang to implement the timer mechanism. You can use the (duration) function to create a timer that triggers a timed event after the duration time. For example:

 timer := ( * 2)
 <-
 ("Timer expired")

This code creates a timer, setting the time to 2 seconds. When the timer reaches 2 seconds, a timed event will be sent to the channel. The program waits for the arrival of the timed event through the <- statement, and outputs "Timer expired" after receiving the timed event.

5. Transmission of Golang channel

In Golang, a channel is a reference type that can be passed like a normal variable. For example:

 func worker(ch chan int) {
     data := <-ch
     (data)
 }

 func main() {
     ch := make(chan int)
     go worker(ch)
     ch <- 1
     ()
 }

In this code, a channel named ch is created in the main function, and a worker goroutine is started, writing a data of 1 into the ch channel. The data is read from the ch channel through the <-ch statement in the worker goroutine and output it to the console.

6. One-way channel

In Golang, you can restrict the read and write operations of a channel by using a one-way channel. The one-way channel only allows read or write operations, and does not allow simultaneously read and write operations. For example:

 func producer(ch chan<- int) {
     ch <- 1
 }

 func consumer(ch <-chan int) {
     data := <-ch
     (data)
 }

 func main() {
     ch := make(chan int)
     go producer(ch)
     go consumer(ch)
     ()
 }

In this code, the producer function and the consumer function are used to write data into and read data from the channel, respectively. In the function parameters, one-way channel restriction parameters are used to read and write operations. In the main function, a channel named ch is created, and a producer goroutine and a consumer goroutine are started. The producer writes data 1 into the ch channel, and the consumer reads data from the ch channel and outputs it to the console.

7. Close the channel

In Golang, you can use the close function to close the channel. After closing the channel, the channel's read and write operation will fail, the read channel will get a zero value, and writing to the channel will cause a panic exception. For example:

 ch := make(chan int)
 go func() {
     for i := 0; i < 5; i++ {
         ch <- i
     }
     close(ch)
 }()
 for data := range ch {
     (data)
 }

In this code, a channel named ch is created, and data 0 to 4 is written into the channel in a goroutine, and the channel is closed through the close function. In the main goroutine, the data in the channel is read loop through the for...range statement and output to the console. When the channel is closed, the for...range statement will automatically exit the loop.

After closing the channel, you can still read the existing data from the channel, for example:

 ch := make(chan int)
 go func() {
     for i := 0; i < 5; i++ {
         ch <- i
     }
     close(ch)
 }()
 for {
     data, ok := <-ch
     if !ok {
         break
     }
     (data)
 }

In this code, the data in the channel is read loopfully and determines whether the channel has been closed. When the channel is closed, the read operation will fail and the value of ok will become false, thus exiting the loop.

8. Common application scenarios

Channels are an important part of Golang concurrent programming, and their common application scenarios include:

8.1 Synchronous data transmission

Channels can be used to synchronize data between different goroutines. When a goroutine needs to wait for the result of another goroutine, the channel can be used for data transfer. For example:

 package main

 import "fmt"

 func calculate(a, b int, result chan int) {
     result <- a + b
 }

 func main() {
     result := make(chan int)
     go calculate(10, 20, result)
     (<-result)
 }

In this example, we use the channel to perform the calculation of a+b and send the result to the main function. In the main function, we wait for the result in the channel and output it.

8.2 Coordinate multiple goroutines

Channels can also be used to coordinate operations between multiple goroutines. For example, in a producer-consumer model, the channel can act as a buffer between the producer and the consumer, coordinating the production and consumption of data. For example:

 package main

 import (
     "fmt"
     "sync"
 )

 func worker(id int, jobs &lt;-chan int, results chan&lt;- int) {
     for j := range jobs {
         ("worker", id, "processing job", j)
         results &lt;- j * 2
     }
 }

 func main() {
     jobs := make(chan int, 100)
     results := make(chan int, 100)

     // Turn on three worker goroutines     for w := 1; w &lt;= 3; w++ {
         go worker(w, jobs, results)
     }

     // Send 9 tasks to the jobs channel     for j := 1; j &lt;= 9; j++ {
         jobs &lt;- j
     }
     close(jobs)

     // Output the results of each task     for a := 1; a &lt;= 9; a++ {
         &lt;-results
     }
 }

In this example, we use channels to coordinate task processing between three worker goroutines. Each worker goroutine gets the task from the jobs channel and sends the processing results to the results channel. The main function is responsible for sending all tasks to the jobs channel and waiting for the results of all tasks to be returned.

8.3 Control concurrent access

When multiple goroutines require concurrent access to certain shared resources, the channel can be used to control concurrent access. By using channels, multiple goroutines can be avoided to access shared resources simultaneously, thereby improving program reliability and performance. For example:

 package main

 import (
     "fmt"
     "sync"
 )

 var (
     balance int
     wg      
     mutex   
 )

 func deposit(amount int) {
     ()
     balance += amount
     ()
     ()
 }

 func main() {
     for i := 0; i < 1000; i++ {
         (1)
         go deposit(100)
     }
     ()
     ("balance:", balance)
 }

In this example, we use mutex to control concurrent access to balance variables. Each goroutine is responsible for depositing RMB 100 into the balance variable. Using a mutex ensures that only one goroutine can access the balance variable at any moment.

8.4 Simulated event-driven

Channels can also be used to simulate event-driven mechanisms. For example, a channel can be used to simulate an event queue, when an event occurs, the event data can be put into the channel and then processed through another goroutine. For example:

 package main

 import (
     "fmt"
     "time"
 )

 func eventLoop(eventChan &lt;-chan string) {
     for {
         select {
         case event := &lt;-eventChan:
             ("Event received:", event)
         case &lt;-(5 * ):
             ("Timeout reached")
             return
         }
     }
 }

 func main() {
     eventChan := make(chan string)

     // Simulate events occur     go func() {
         (2 * )
         eventChan &lt;- "Event 1"
         (1 * )
         eventChan &lt;- "Event 2"
         
         1 * 
         eventChan &lt;- "Event 3"
         (4 * )
         eventChan &lt;- "Event 4"
     }()
     eventLoop(eventChan)
 }

In this example, we use channels to simulate the occurrence of events. The eventLoop function uses the select statement to listen for the eventChan channel and the 5-second timeout event. When eventChan receives an event, the eventLoop function prints the event out. If no event is received within 5 seconds, the eventLoop function ends. The main function is responsible for creating the eventChan channel and simulating the occurrence of events.

8.5 Batch processing tasks

 package main

 import (
     "fmt"
     "sync"
 )

 func processTask(task int) {
     ("Processing task", task)
 }

 func main() {
     tasks := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
     // Define batch processing function with concurrency of 3     batchSize := 3
     var wg 
     taskChan := make(chan int)
     for i := 0; i &lt; batchSize; i++ {
         (1)
         go func() {
             defer ()
             for task := range taskChan {
                 processTask(task)
             }
         }()
     }

     // Distribute the task to the taskChan channel     for _, task := range tasks {
         taskChan &lt;- task
     }
     close(taskChan)

     ()
 }

In this example, we use channels to batch tasks. First, an array containing 10 tasks is defined. We then define a batch processing function with a concurrency of 3, which takes the task from the taskChan channel and outputs the task processing results. The main function is responsible for sending all tasks into the taskChan channel and waiting for all tasks to be processed. Note that we used the goroutine to wait for all batch processing functions to end.

8.6 Implementing the publish/subscribe mode

 package main

 import "fmt"

 type eventBus struct {
     subscriptions map[string][]chan string
 }

 func newEventBus() *eventBus {
     return &amp;eventBus{
         subscriptions: make(map[string][]chan string),
     }
 }

 func (eb *eventBus) subscribe(eventType string, ch chan string) {
     [eventType] = append([eventType], ch)
 }

 func (eb *eventBus) unsubscribe(eventType string, ch chan string) {
     subs := [eventType]
     for i, sub := range subs {
         if sub == ch {
             subs[i] = nil
             [eventType] = subs[:i+copy(subs[i:], subs[i+1:])]
             break
         }
     }
 }

 func (eb *eventBus) publish(eventType string, data string) {
     for _, ch := range [eventType] {
         if ch != nil {
             ch &lt;- data
         }
     }
 }

 func main() {
     eb := newEventBus()

     ch1 := make(chan string)
     ch2 := make(chan string)

     ("event1", ch1)
     ("event2", ch2)

     go func() {
         for {
             select {
             case data := &lt;-ch1:
                 ("Received event1:", data)
             case data := &lt;-ch2:
                 ("Received event2:", data)
             }
         }
     }()

     ("event1", "Event 1 data")
     ("event2", "Event 2 data")

     ("event1", ch1)

     ("event1", "Event 1 data after unsubscribe")

     // Wait for event processing to complete     ()
 }

In this example, we use channels to implement the publish/subscribe mode. An eventBus structure is defined, which contains a subscriptions map to store event types and all channels that subscribe to that event type. We can add a subscription channel to an event type through the subscribe function, unsubscribe the channel through the unsubscribe function, and publish events to an event type through the publish function.

In the main function, we create two channels, ch1 and ch2, and subscribe to the "event1" and "event2" event types through the subscribe function. Then, we start a goroutine, listen to the ch1 and ch2 channels using the select statement, and print out the received events. Next, we use the publish function to publish events to "event1" and "event2" respectively. Finally, we unsubscribe the ch1 channel of the "event1" event type using the unsubscribe function, and again we published the event to "event1" using the publish function. Note that we used () to wait for event processing to complete to avoid the program exiting before event processing is completed.

9. Summary

Channels are a very important concurrency primitive in Go, which can effectively manage concurrent access to shared data and avoid data competition. Through the channel, synchronous and asynchronous message delivery can be realized to communicate between different goroutines. When using channels, you need to pay attention to the basic syntax of the channel, buffering mechanism, timeout and timer, channel transfer, one-way channel and closing channels, and select appropriate channel modes according to the actual scenario to improve the concurrency performance and stability of the program.

The above is the detailed content of the super practical Golang channel guide to easily implement concurrent programming. For more information about the Golang channel implementation, please pay attention to my other related articles!