Go Basics - concurrency

Keywords: Go

Go Basics - Contents

(1) Go start
(2) Variable
(3) Operators, process control
(4) Functions
(5) Container
(6) Structure
(7) Concurrent

preface

Multiple tasks can be executed at the same time. Concurrent programming has a wide meaning, including multi-threaded programming, multi process programming and distributed programming. The meaning of concurrency explained in this chapter belongs to multi-threaded programming.

The Go language supports concurrency through the compiler runtime. The concurrency of Go language is completed through the goroutine feature. Goroutines are similar to threads, but you can create multiple goroutines to work concurrently as needed. Goroutine is scheduled by the run-time of Go language, while threads are scheduled by the operating system.

The Go language also provides a channel for communication between multiple goroutines. Goroutine and channel are the important implementation basis of CSP (communication sequential process) concurrency mode inherited by Go language. In this chapter, we will explain goroutine, channel and related features in detail.

1, Advantages of concurrency

Go supports concurrency at the language level. At the same time, the automatic garbage collection mechanism is realized.

Process / thread

  • Process is an execution process of a program in the operating system. The system is an independent unit for resource allocation and scheduling.
  • Thread is an execution entity of a process. It is the basic unit of CPU scheduling and dispatching. It is a smaller basic unit that can run independently than a process.
  • A process can create and undo multiple threads, and multiple threads in the same process can execute concurrently.

Concurrency / parallelism

  • Multithreaded programs run on a single core cpu, which is called concurrency; Multithreaded programs run on multi-core CPUs, which is called parallelism.
  • Concurrency is different from parallelism. Concurrency mainly realizes "simultaneous" operation by switching time slices. Parallelism directly uses multi-core to realize multi-threaded operation. Go program can set the number of cores to give full play to the ability of multi-core computer.

Coroutine / thread

  • Coroutine: independent stack space and shared heap space. The scheduling is controlled by the user. In essence, it is somewhat similar to user level threads. The scheduling of these user level threads is also realized by themselves.
  • Thread: a thread can run multiple coroutines. Coroutines are lightweight threads.

Elegant concurrent programming paradigm, perfect concurrency support and excellent concurrency performance are a major feature that distinguishes Go language from other languages. When developing server programs with Go language, we need to have an in-depth understanding of its concurrency mechanism.

1.1 Goroutine

goroutine is a very lightweight implementation, which can execute thousands of concurrent tasks in a single process. It is the core of concurrency design of Go language.

In the final analysis, goroutine is actually a thread, but it is smaller than a thread. More than a dozen goroutines may be reflected in five or six threads at the bottom, and the memory sharing between goroutines is also realized within the Go language.

Use the Go keyword to create a goroutine. Put the Go declaration before a function to be called, and call and run the function in the same address space. In this way, the function will be executed as an independent concurrent thread. This thread is called goroutine in the Go language.

//Put the go keyword before the method call, create a new goroutine and execute the method body
go GetThingDone(param1, param2);
//Create an anonymous method and execute it
go func(param1, param2) {
}(val1, val2)
//Directly create a goroutine and execute the code block in goroutine
go {
    //do someting...
}

Because goroutines are parallel in multi-core cpu environment, if code blocks are executed in multiple goroutines, we can realize code parallelism.

If you need to know the execution of the program, how can you get parallel results? You need to use channel in combination.

1.2 channel

Channel is the communication mode between goroutines provided by Go language at the language level. We can use channel to pass messages between two or more goroutines.

Channel is an intra process communication mode, so the process of passing objects through channel is consistent with the parameter passing behavior when calling functions, such as passing pointers. If cross process communication is required, we suggest using distributed system methods, such as Socket or HTTP. Go language also has perfect support for network.

Channel is type dependent, that is, a channel can only pass a value of one type, which needs to be specified when declaring the channel. If you know something about Unix pipeline, it is not difficult to understand channel, which can be regarded as a type safe pipeline.

When defining a channel, you also need to define the type of value sent to the channel. Note that you must use make to create a channel. The code is as follows:

ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

2, goroutine lightweight thread

2.1 create goroutine using normal functions

go Function name( parameter list )
// Function name: the name of the function to be called.
// Parameter list: the parameters that need to be passed in to call the function.
package main
import (
    "fmt"
    "time"
)
func running() {
    var times int
    // Build an infinite loop
    for {
        times++
        fmt.Println("tick", times)
        // Delay 1 second
        time.Sleep(time.Second)
    }
}
func main() {
    // Concurrent execution program
    go running()  // Use the go keyword to make the running() function run concurrently
    // Accept command line input and do nothing
    var input string
    fmt.Scanln(&input)
}

2.2 creating goruntine using anonymous functions

go func( parameter list ){
    Function body
}( Call parameter list )
package main
import (
    "fmt"
    "time"
)
func main() {
    go func() {
        var times int
        for {
            times++
            fmt.Println("tick", times)
            time.Sleep(time.Second)
        }
    }()
    var input string
    fmt.Scanln(&input)
}

3, Concurrent communication

In engineering, there are two most common concurrent communication models: shared data and message.

Shared data means that multiple concurrent units keep references to the same data to share the data. Shared data may take many forms, such as memory data blocks, disk files, network data, etc. In practical engineering applications, the most common is undoubtedly memory, which is often referred to as shared memory.

package main
import (
    "fmt"
    "runtime"
    "sync"
)
var counter int = 0
func Count(lock *sync.Mutex) {
    lock.Lock()
    counter++
    fmt.Println(counter)
    lock.Unlock()
}
func main() {
    lock := &sync.Mutex{}
    for i := 0; i < 10; i++ {
        go Count(lock)
    }
    for {
        lock.Lock()
        c := counter
        lock.Unlock()
        runtime.Gosched()
        if c >= 10 {
            break
        }
    }
}

We shared the variable counter in 10 goroutines. After each goroutine is executed, the value of counter will be increased by 1. Because 10 goroutines are executed concurrently, we also introduce locks, that is, lock variables in the code. For each operation of n, lock the lock first, and then open the lock after the operation is completed.

In the main function, use the for loop to constantly check the value of counter (also need to lock). When the value reaches 10, it means that all goroutine s have been executed. At this time, the main function returns and the program exits.

Things seem to be getting worse. Realize such a simple function, but write such bloated and incomprehensible code. Imagine that there are numerous locks, numerous shared variables, numerous business logic and error handling branches in a large system, which will be a nightmare. This nightmare is what many C/C + + developers are experiencing. In fact, Java and c# developers are not much better.

Since go language takes concurrent programming as the core advantage of the language, of course, it will not solve such problems in such a helpless way. Go language provides another communication model, that is, the message mechanism rather than shared memory is used as the communication mode.

Message mechanism considers that each concurrent unit is a self-contained and independent individual, and has its own variables, but these variables are not shared among different concurrent units. There is only one input and output for each concurrent unit, that is, message. This is a bit similar to the concept of process. Each process will not be disturbed by other processes. It can only do its own work. Different processes communicate by messages, and they do not share memory.

The message communication mechanism provided by Go language is called channel.

4, Competitive state

If there is concurrency, there is resource competition. If two or more goroutine s access a shared resource without synchronizing with each other, for example, when reading and writing to the resource at the same time, they will be in a competitive state. This is resource competition in concurrency.

Concurrency itself is not complicated, but the problem of resource competition makes it complicated for us to develop good concurrent programs, because it will cause many inexplicable problems.

The reading and writing of the same resource must be atomic, that is, only one goroutine can read and write shared resources at the same time.

The problem of shared resource competition is very complex and difficult to detect. Go provides a tool to help us check. This is the go build -race command. Execute this command in the project directory to generate an executable file, and then run the executable file to see the printed detection information.

4.1 lock shared resources

Go language provides the traditional mechanism of synchronizing goroutine, that is, locking shared resources. Some functions in atomic and sync packages can lock shared resources.

4.1.1 atomic function

Atomic functions can synchronously access integer variables and pointers with a very low-level locking mechanism. The example code is as follows:

package main
import (
    "fmt"
    "runtime"
    "sync"
    "sync/atomic"
)
var (
    counter int64
    wg      sync.WaitGroup
)
func main() {
    wg.Add(2)
    go incCounter(1)
    go incCounter(2)
    wg.Wait() //Wait for goroutine to finish
    fmt.Println(counter)
}
func incCounter(id int) {
    defer wg.Done()
    for count := 0; count < 2; count++ {
        atomic.AddInt64(&counter, 1) //Safely add 1 to counter
        runtime.Gosched()
    }
}

The AddInt64 function of atmoic package is used in the above code. This function will synchronize the addition of integer values by forcing only one gorountie to run and complete the addition operation at the same time. When goroutines try to call any atomic function, these goroutines will automatically synchronize according to the referenced variables.

Two other useful atomic functions are LoadInt64 and StoreInt64. These two functions provide a safe way to read and write an integer value. The following code uses the LoadInt64 and StoreInt64 functions to create a synchronization flag, which can notify multiple goroutine s in the program of a special status.

package main
import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)
var (
    shutdown int64
    wg       sync.WaitGroup
)
func main() {
    wg.Add(2)
    go doWork("A")
    go doWork("B")
    time.Sleep(1 * time.Second)
    fmt.Println("Shutdown Now")
    atomic.StoreInt64(&shutdown, 1)
    wg.Wait()
}
func doWork(name string) {
    defer wg.Done()
    for {
        fmt.Printf("Doing %s Work\n", name)
        time.Sleep(250 * time.Millisecond)
        if atomic.LoadInt64(&shutdown) == 1 {
            fmt.Printf("Shutting %s Down\n", name)
            break
        }
    }
}

The main function in the above code uses the StoreInt64 function to safely modify the value of the shutdown variable. If a doWork goroutine tries to call the LoadInt64 function while the main function calls StoreInt64, the atomic function will synchronize these calls with each other to ensure that these operations are safe and will not enter the competition state.

4.1.2 mutex

Another way to access shared resources synchronously is to use mutex. The name mutex comes from the concept of mutex. Mutex is used to create a critical area on the code to ensure that only one goroutine can execute the critical code at the same time.

package main
import (
    "fmt"
    "runtime"
    "sync"
)
var (
    counter int64
    wg      sync.WaitGroup
    mutex   sync.Mutex
)
func main() {
    wg.Add(2)
    go incCounter(1)
    go incCounter(2)
    wg.Wait()
    fmt.Println(counter)
}
func incCounter(id int) {
    defer wg.Done()
    for count := 0; count < 2; count++ {
        //Only one goroutine is allowed to enter the critical zone at the same time
        mutex.Lock()
        {
            value := counter
            runtime.Gosched()
            value++
            counter = value
        }
        mutex.Unlock() //Release the lock to allow other waiting goroutine s to enter the critical area
    }
}

Only one goroutine can enter the critical zone at the same time. Then, other goroutines cannot enter the critical area until the Unlock function is called. When the runtime.Gosched function is called to force the current goroutine to exit the current thread, the dispatcher will allocate the goroutine again to continue running.

5, GOMAXPROCS adjusts the performance of concurrent operations

A small task scheduler is implemented in the go language program runtime. The working principle of this scheduler is similar to that of the operating system scheduling threads. The Go program scheduler can efficiently allocate CPU resources to each task. In traditional logic, developers need to maintain the corresponding relationship between the number of threads in the thread pool and the number of CPU cores. Similarly, you can also use the runtime.GOMAXPROCS() function in go. The format is:

runtime.GOMAXPROCS(logic CPU quantity)

The number of logical CPU s can be as follows:

  • < 1: do not modify any value.
  • =1: Single core execution.
  • 1: Multi core concurrent execution.

In general, you can use runtime.NumCPU() to query the number of CPU s and use the runtime.GOMAXPROCS() function to set, for example:

runtime.GOMAXPROCS(runtime.NumCPU())
// Before Go 1.5, single core execution was used by default. Starting from go version 1.5, the above statements are executed by default to allow code to execute concurrently and maximize CPU efficiency.
// GOMAXPROCS is also an environment variable. Setting environment variables before application startup can also play the same role.

6, The difference between concurrency and parallelism

  • concurrency: the task is handed over to the processor for processing at different time points. At the same point in time, tasks do not run at the same time.
  • parallelism: each task is assigned to each processor and completed independently. At the same time point, tasks must be running at the same time.

Concurrency is not parallelism. Parallelism is to let different code fragments execute on different physical processors at the same time. The key to parallelism is to do many things at the same time, while concurrency refers to managing many things at the same time. These things may be suspended to do other things after only half of them are done.

In many cases, the effect of concurrency is better than parallelism, because the total resources of operating system and hardware are generally small, but it can support the system to do many things at the same time. This philosophy of "using less resources to do more" is also the philosophy guiding Go language design.

If you want goroutine to be parallel, you must use more than one logical processor. When there are multiple logical processors, the scheduler will equally allocate goroutine to each logical processor. This allows goroutine to run on different threads. However, to really achieve the effect of parallelism, users need to run their programs on a machine with multiple physical processors. Otherwise, even if multiple threads are used in the Go language runtime, goroutine will still run concurrently on the same physical processor, which can not achieve the effect of parallelism.

Go language can execute in parallel when the number of GOMAXPROCS is equal to the number of tasks, but generally it is executed concurrently.

7, The difference between goruntine and coroutine
C #, Lua and Python all support coroutine feature. Coroutine and goroutine have similar names. They can run functions or statements in an independent environment, but they are different in two points:

  • goroutine may execute in parallel;
  • coroutine is always executed sequentially.

Goroutines means parallel (or can be deployed in parallel). Generally speaking, coroutines are not like this. Goroutines communicate through channels; Coroutines communicate through surrender and recovery operations. Goroutines are more powerful than coroutines and can be easily reused from the logic of coroutines to 'goroutines'.

In a narrow sense, goroutine may occur in a multithreaded environment, and goroutine cannot control itself to obtain high priority support; Coroutine always occurs in a single thread. The coroutine program needs to actively hand over control, so that the host can obtain control and hand over control to other coroutines.

channel communication is used between goroutine s, and yield and resume are used by coroutine s.

The concept and operation mechanism of goroutine and coroutine are derived from the early operating system.

The operating mechanism of coroutine belongs to collaborative task processing. In the early operating system, every application must abide by the task processing rules of the operating system. When the application does not need to use CPU, it will take the initiative to hand over the CPU usage right. If developers inadvertently or deliberately let applications occupy CPU for a long time, the operating system can do nothing. The effect is that the computer is easy to lose response or crash.

goroutine belongs to preemptive task processing, which is very similar to the existing multi-threaded and multi process task processing. The control of the application program over the CPU ultimately needs to be managed by the operating system. If the operating system finds that an application program occupies a large amount of CPU for a long time, the user has the right to terminate this task.

7, Channel (chan) the channel for communication between goroutine s

If goroutine is the concurrent body of Go language programs, channels is the communication mechanism between them.

A channel is a communication mechanism that allows one goroutine to send value information to another goroutine through it. Each channel has a special type, that is, the type of data that channels can send. A channel that can send int type data is generally written as chan int.

Go language advocates the use of communication instead of shared memory. When a resource needs to be shared between goroutines, the channel sets up a pipeline between goroutines and provides a mechanism to ensure synchronous data exchange. When declaring a channel, you need to specify the type of data to be shared. You can share values or pointers of built-in types, named types, structure types, and reference types through channels.

7.1 channel characteristics

Channel in Go language is a special type. At any time, only one goroutine access channel can send and obtain data. Goroutines can communicate through channels.

The channel is like a conveyor belt or queue. It always follows the First In First Out rule to ensure the order of sending and receiving data.

7.2 declare channel type

var Channel variable chan Channel type

Note: the null value of chan type is nil. It can only be used after declaration in conjunction with make.

7.3 creating channels

The channel is a reference type and needs to be created using make. The format is as follows:

Channel instance := make(chan data type)
ch1 := make(chan int)                 // Create a channel of integer type
ch2 := make(chan interface{})         // Create a channel of empty interface type, which can store any format
type Equip struct{ /* Some fields */ }
ch2 := make(chan *Equip)             // Create a channel of Equip pointer type, which can store * Equip

7.4 sending data using channels

7.4.1 format of channel transmission data

The special operator < -, is used to send data through the channel. The format of sending data through the channel is:

Channel variable <- value
// Channel variable: create a good channel instance through make.
// Value: it can be a variable, constant, expression or function return value. The type of the value must match the element type of the ch channel.
// Create an empty interface channel
ch := make(chan interface{})
// Put 0 into channel
ch <- 0
// Put the hello string into the channel
ch <- "hello"

7.4.2 transmission will continue to block until data is received

When sending data to the channel, if the receiver has not received it all the time, the sending operation will continue to be blocked. When the Go program runs, it can intelligently find some statements that can never be sent successfully and give prompts.

7.5 receiving data using channels

The < - operator is also used for channel reception. Channel reception has the following characteristics:

  • The transceiver operation of the channel is carried out between two different goroutine s.
  • Reception will continue to block until the sender sends data.
  • Receive one element at a time.

7.5.1 blocking received data

When receiving data in blocking mode, take the received variable as the lvalue of < - operator, with the format as follows:

data := <-ch

7.5.2 non blocking receiving data

When receiving data from a channel in a non blocking manner, the statement will not be blocked. The format is as follows:

data, ok := <-ch
// Data: indicates the received data. When no data is received, data is the zero value of the channel type.
// ok: indicates whether data is received

// The non blocking channel receiving method may cause high CPU consumption, so it is used very little. If receiving timeout detection is required, it can be carried out in conjunction with select and timer channel.

7.5.3 receive any data and ignore the received data

After blocking the received data, ignore the data returned from the channel. The format is as follows:

<-ch

Blocking occurs when the statement is executed until data is received, but the received data is ignored. In fact, this method only blocks the sending and receiving between goroutine s through channels to realize concurrent synchronization.

// Concurrent synchronization using channels
package main
import (
    "fmt"
)
func main() {
    // Build a channel for synchronization
    ch := make(chan int)
    // Open a concurrent anonymous function
    go func() {
        fmt.Println("start goroutine")
        // Notify goroutine of main through channel
        // When the anonymous goroutine is about to end, notify the goroutine of main through the channel. This sentence will be blocked until the goroutine of main is received
        ch <- 0
        fmt.Println("exit goroutine")
    }()
    fmt.Println("wait goroutine")
    // Waiting for anonymous goroutine
    // After starting goroutine, wait for the anonymous goroutine to end through the channel immediately
    <-ch
    fmt.Println("all done")
}

7.5.4 cyclic reception

For the data reception of channels, the for range statement can be used to receive multiple elements. The format is as follows:

for data := range ch {
}
// Channel ch can be traversed, and the result of traversal is the received data. The data type is the data type of the channel. There is only one variable obtained through the for traversal, data.
package main
import (
    "fmt"
    "time"
)
func main() {
    // Build a channel
    ch := make(chan int)
    // Open a concurrent anonymous function
    go func() {
        // Loop from 3 to 0
        for i := 3; i >= 0; i-- {
            // Send a value between 3 and 0
            ch <- i
            // Wait at the end of each transmission
            time.Sleep(time.Second)
        }
    }()
    // Traverse receive channel data
    for data := range ch {
        // Print channel data
        fmt.Println(data)
        // When data 0 is encountered, exit the receiving cycle
        // When the value 0 is received, the reception is stopped. If you continue to send, because the receiving goroutine has exited and no goroutine is sent to the channel, the runtime will trigger a downtime error
        if data == 0 {
                break
        }
    }
}

8, Unidirectional channel

8.1 declaration format of one-way channel

var Channel instance chan<- Element type    // Channels that can only write data
var Channel instance <-chan Element type    // Channels that can only read data
ch := make(chan int)
// Declare a channel type that can only write data and assign it to ch
var chSendOnly chan<- int = ch
//Declare a channel type that can only read data and assign it to ch
var chRecvOnly <-chan int = ch

8.2 one way channel in time package

The timer in the time package will return a timer instance. The code is as follows:

timer := time.NewTimer(time.Second)
// The Timer class of Timer is defined as follows
type Timer struct {
    C <-chan Time
    r runtimeTimer
}
// The type of C channel is a one-way channel that can only be read.
// If the channel direction constraint is not performed here, once the external data is written to the channel, the logic of other places using the timer will be confused

One way channel is conducive to the rigor of code interface.

8.3 close channel

close(ch)
// Judge whether a channel has been closed
x, ok := <-ch

9, Unbuffered channel

unbuffered channel in Go language refers to a channel that does not have the ability to save any value before receiving. This type of channel requires both sending goroutine and receiving goroutine to be prepared at the same time, so as to complete the sending and receiving operations.

// This sample program shows how to simulate with an unbuffered channel
// Tennis match between 2 goroutine s
package main
import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)
// wg is used to wait for the program to end
var wg sync.WaitGroup
func init() {
    rand.Seed(time.Now().UnixNano())
}
// main is the entrance to all Go programs
func main() {
    // Create an unbuffered channel
    court := make(chan int)
    // Add 2 to the count to wait for two goroutine s
    wg.Add(2)
    // Start two players
    go player("Nadal", court)
    go player("Djokovic", court)
    // serve
    court <- 1
    // Wait for the game to end
    wg.Wait()
}
// Player simulates a player playing tennis
func player(name string, court chan int) {
    // When the function exits, call Done to notify the main function that the work has been completed
    defer wg.Done()
    for {
        // Wait for the ball to be hit
        ball, ok := <-court
        if !ok {
            // If the channel is closed, we win
            fmt.Printf("Player %s Won\n", name)
            return
        }
        // Choose a random number and use this number to judge whether we lose the ball
        n := rand.Intn(100)
        if n%13 == 0 {
            fmt.Printf("Player %s Missed\n", name)
            // Closing the channel means we lost
            close(court)
            return
        }
        // Displays the number of shots and adds 1 to the number of shots
        fmt.Printf("Player %s Hit %d\n", name, ball)
        ball++
        // Hit the ball at the opponent
        court <- ball
    }
}
// This sample program shows how to simulate with an unbuffered channel
// Relay race between four goroutine s
package main
import (
    "fmt"
    "sync"
    "time"
)
// wg is used to wait for the program to end
var wg sync.WaitGroup
// main is the entrance to all Go programs
func main() {
    // Create an unbuffered channel
    baton := make(chan int)
    // Add 1 to the count for the last runner
    wg.Add(1)
    // The first runner holds the baton
    go Runner(baton)
    // Start the game
    baton <- 1
    // Wait for the end of the game
    wg.Wait()
}
// Runner is a runner in a simulated relay race
func Runner(baton chan int) {
    var newRunner int
    // Waiting for the baton
    runner := <-baton
    // Start running around the track
    fmt.Printf("Runner %d Running With Baton\n", runner)
    // Create next runner
    if runner != 4 {
        newRunner = runner + 1
        fmt.Printf("Runner %d To The Line\n", newRunner)
        go Runner(baton)
    }
    // Run around the track
    time.Sleep(100 * time.Millisecond)
    // Is the game over?
    if runner == 4 {
        fmt.Printf("Runner %d Finished, Race Over\n", runner)
        wg.Done()
        return
    }
    // Give the baton to the next runner
    fmt.Printf("Runner %d Exchange With Runner %d\n",
        runner,
        newRunner)
    baton <- newRunner
}

10, Buffered channel

buffered channel in o language is a channel that can store one or more values before being received. This type of channel does not require goroutine s to send and receive at the same time. The channel will be blocked, and the conditions for sending and receiving actions will be different. The receive action is blocked only if there is no value to receive in the channel. The send action is blocked only when the channel has no available buffer to hold the sent value.

This leads to a big difference between buffered channels and unbuffered channels: unbuffered channels ensure that the goroutine s transmitting and receiving will exchange data at the same time; Buffered channels do not have this guarantee.

On the basis of non buffered channel, a limited storage space is added to the channel to form a buffered channel. When sending, the buffered channel can complete the sending process without waiting for the receiver to receive, and blocking will not occur. Blocking will occur only when the storage space is full. Similarly, if there is data in the buffer channel, blocking will not occur when receiving. Until there is no data readable in the channel, the channel will block again.

There is no buffer channel to ensure the synchronization of transceiver process. The unbuffered sending and receiving process is similar to the courier calling you to go downstairs to pick up the express. The whole process of delivering the express occurs synchronously. You and the courier will see each other. But in this way, the courier must wait for everyone to complete the operation downstairs before completing all the delivery work. If the courier puts the express into the express cabinet and notifies the user to pick it up, the courier and the user become an asynchronous sending and receiving process, and the efficiency can be significantly improved. The channel with buffer is such a "express cabinet".

10.1 creating buffered channels

Channel instance := make(chan Channel type, Buffer size)
package main
import "fmt"
func main() {
    // Create an integer channel with 3 element buffer sizes
    ch := make(chan int, 3)
    // View the size of the current channel
    fmt.Println(len(ch))
    // Send 3 integer elements to channel
    ch <- 1
    ch <- 2
    ch <- 3
    // View the size of the current channel
    fmt.Println(len(ch))
}

10.2 blocking conditions

Buffered channels are similar to unbuffered channels in many characteristics. Unbuffered channels can be regarded as buffered channels with a length of always 0. Therefore, according to this feature, the buffered channel will still block under the following conditions:

  • When the buffered channel is full, blocking occurs when trying to send data again.
  • When the buffered channel is empty, blocking occurs when trying to receive data.

Why does the Go language limit the length of channels instead of providing infinite length channels?
A channel is a bridge for communication between two goroutines. The code using goroutine must have one party providing data and the other party consuming data. When the data supply speed of the data provider is greater than the data processing speed of the consumer, if the channel does not limit the length, the memory will continue to expand until the application crashes. Therefore, limiting the length of the channel is conducive to restricting the supply speed of the data provider. The supplied data must be within the range of the processing capacity of the consumer + the length of the channel in order to process the data normally.

11, channel timeout mechanism

You can use select to set the timeout.

Although the select mechanism is not specifically designed for timeout, it can easily solve the timeout problem, because the feature of select is that as long as one case has been completed, the program will continue to execute without considering the situation of other cases.

The usage of select is very similar to the switch language. A new selection block is started by select, and each selection condition is described by a case statement.

Compared with the switch statement, select has many restrictions. One of the biggest restrictions is that each case statement must have an IO operation. The general structure is as follows:

select {
    case <-chan1:
    // If chan1 successfully reads the data, the case processing statement is executed
    case chan2 <- 1:
    // If the data is successfully written to chan2, the case processing statement is executed
    default:
    // If none of the above is successful, enter the default process
}

In a select statement, the Go language evaluates each statement sent and received from beginning to end in order.

If any one of the statements can continue to be executed (that is, it is not blocked), select any one from those executable statements to use.

If no statement can be executed (that is, all channels are blocked), there are two possible situations:

  • If the default statement is given, the default statement will be executed, and the execution of the program will be recovered from the statement after the select statement;
  • If there is no default statement, the select statement will be blocked until at least one communication can proceed.
package main
import (
    "fmt"
    "time"
)
func main() {
    ch := make(chan int)
    quit := make(chan bool)
    //Open a new cooperation process
    go func() {
        for {
            select {
            case num := <-ch:
                fmt.Println("num = ", num)
            case <-time.After(3 * time.Second):
                fmt.Println("overtime")
                quit <- true
            }
        }
    }() //Don't forget ()
    for i := 0; i < 5; i++ {
        ch <- i
        time.Sleep(time.Second)
    }
    <-quit
    fmt.Println("Program end")
}

Posted by tgavin on Thu, 28 Oct 2021 13:37:07 -0700