1. Introduction
This article will explain why you need to actively close it firstgoroutine
, and explain how to close it in Gogoroutine
Common routines include passing termination signals and coroutine internal capture termination signals. Afterwards, the article lists common scenarios where coroutines need to actively close their operation, such as starting a coroutine to perform a repetitive task. I hope that through the introduction of this article, readers can understand how to close it at the right time.goroutine
, and learn about the closinggoroutine
common routines.
2. Why do you need to close goroutine
2.1 The life cycle of coroutines
Understanding the life cycle of a coroutine is a prerequisite for gracefully closing a coroutine, because the current state of the coroutine needs to be known before closing a coroutine in order to take appropriate measures. So we need to understand it firstgoroutine
life cycle.
existGo
In the language, a coroutine is a lightweight thread that can run multiple coroutines at the same time in a program to improve the concurrency performance of the program. The life cycle of a coroutine includes three stages: creation, operation and ending.
First, you need to create a coroutine. The creation of the coroutine can be achieved through the keyword go, for example:
go func() { // Code executed by coroutines}()
The above code will start a new coroutine and execute anonymous functions in the new coroutine, and the coroutine has been created.
Once the coroutine is created, it will run in a new thread. The running status of a coroutine can be managed by the Go runtime (goroutine scheduler), which will automatically schedule the coroutine to the appropriate one.P
Run in and ensure fair scheduling and balanced loads of coroutines.
During the run phase, the coroutine will continue to execute tasks until the task is completed or a termination condition is encountered. During the termination phase, the coroutine will be recycled, completing its entire life cycle.
To sum up, coroutines arego
The keyword is started, and its business logic is executed in the coroutine until the termination condition is finally encountered. At this time, the task of the coroutine has ended and will enter the termination stage. The coroutine will eventually be recycled.
2.2 Coroutine Termination Conditions
Normally, after the coroutine task is completed, the coroutine will automatically exit, for example:
func main() { var wg (1) go func() { defer () // Code executed by coroutines ("Coprocess execution completed") }() () // Wait for the coroutine to complete execution ("Main program ends")
In the above code, we useWaitGroup
Wait for the coroutine to complete execution. After the coroutine is executed, the program will output two messages: the coroutine is executed and the main program ends.
Another situation is that the coroutine occurs, and it will automatically exit. For example:
func main() { var wg (1) go func() { defer () // Code executed by coroutines panic("An error occurred in coroutine") }() // Wait for the coroutine to complete execution () ("Main program ends") }
In this case, the coroutine will also automatically exit and will no longer occupy system resources.
Overall, the termination condition of a coroutine is actually that the task execution in the coroutine is completed, or a panic occurs during the execution process. The coroutine will meet the termination condition and exit the execution.
2.3 Why do you need to actively close goroutine
Judging from the above coroutine termination conditions, under normal circumstances, as long as the coroutine completes the task normally, the coroutine will automatically exit, and there is no need to actively close it at this time.goroutine
。
Here we first give an example of a producer and a consumer. In this example, we create a producer and a consumer, and they pass through achannel
Conduct communication. Producer produces data and sends it to achannel
In this, consumerschannel
Read data and process it. The code example is as follows:
func main() { // Producer code go func(out chan<- int) { for i := 0; ; i++ { select { case out <- i: ("producer: produced %d\n", i) () } } // Consumer logic go func(in <-chan int) { for { select { case i := <-in: ("consumer: consumed %d\n", i) } } } // Let producer coroutines and consumer coroutines be executed forever (100000000) }
In this example, we use twogoroutine
: Producers and consumers. Producerchannel
In production data, consumers fromchannel
consumption data.
However, if there is a problem with the producer, the producer's coroutine will be exited and will no longer be executed. And the consumer is still waiting for the input of the data. At this time, there is no need for consumer coroutines to exist, but they actually need to exit the execution.
Therefore, for some coroutines that do not meet the termination conditions, they do not need to continue to execute. At this time, they actively close their execution to ensure the robustness and performance of the program.
3. How to close goroutine gracefully
Elegant closegoroutine
We can follow the following three steps to execute. First, it passes a signal to close the coroutine, second, the coroutine needs to be able to close the signal, and finally, when the coroutine exits, it can correctly release the resources it occupies. Through the above steps, you can stop gracefully when neededgoroutine
execution. The following is a detailed explanation of these three steps.
3.1 Passing a shutdown termination signal
First, by givinggoroutine
Pass a signal to close the coroutine, allowing the coroutine to exit. Can be used hereTo pass signals, the specific implementation can be called
WithCancel
,WithDeadline
,WithTimeout
etc. to create a cancel functionContext
and call it when the coroutine needs to be closedCancel
Method toContext
Send a cancel signal. The sample code is as follows:
ctx, cancel := (()) go func(ctx ) { for { select { // After calling the cancel function, you will be able to receive a notification here case <-(): return default: // do something } } }(ctx) // Call the cancel method to send a cancel signal when the coroutine needs to be closedcancel()
Here, when we want to terminate the execution of the coroutine, we only need to call to cancel it.context
The object'sCancel
Methods, the coroutine will be able to passcontext
The object receives a notification to terminate the execution of the coroutine.
3.2 Coroutine internal capture of termination signal
The coroutine also needs to be correctly captured when the signal is cancelled to be transmitted to the coroutine to terminate the process normally. Here we can useselect
Statement to listen for cancel signal.select
There can be multiple statementscase
clauses that can listen to multiplechannel
,whenselect
When the statement is executed, it will block until there is acase
The clause can be executed.select
Statements can also contain default clauses. This clause will be executed when all case clauses cannot be executed, and is usually used to prevent blocking of select statements. as follows:
select { case <-channel: // The channel has code executed when the data arrivesdefault: // Code executed when all channels have no data}
andcontext
The object'sDone
The method also returns achannel
, the cancel signal is through thischannel
to pass. So we can inside the coroutine,select
Statement, in one ofcase
Branches to listen for cancel signals; use onedefault
Branches execute specific business logic in coroutines. When the termination signal has not arrived, the business logic is executed; after receiving the coroutine termination signal, the coroutine execution can also be terminated in time. as follows:
go func(ctx ) { for { select { // After calling the cancel function, you will be able to receive a notification here case <-(): return default: // Execute business logic } } }(ctx)
3.3 Recycling coroutine resources
Finally, when the coroutine is terminated, the occupied resources need to be released, including file handles, memory, etc., so that other programs can continue to use these resources. In Go language, you can usedefer
Statement to ensure that coroutines can correctly release resources when exiting. For example, if a file is opened in the coroutine, you can close it through the defer statement to avoid resource leakage. The code example is as follows:
func doWork() { file, err := ("") if err != nil { (err) } defer () // Do some work }
In this example, we usedefer
The statement registers a function that is automatically called when the coroutine ends to close the file. In this way, whenever the coroutine exits, we can ensure that the file is closed correctly and avoid resource leakage and other problems.
3.4 Close goroutine example
Here is a simple example, combined withContext
Object,select
Statements anddefer
The three parts of the statement are elegantly terminated by the operation of a coroutine. The specific code examples are as follows:
package main import ( "context" "fmt" "time" ) func worker(ctx ) { // Finally, before the coroutine exits, release the resource. defer ("worker stopped") for { // Monitor the cancel signal through the select statement. If the cancel signal fails to arrive, then execute the business logic and wait for the next loop check. select { default: ("working") case <-(): return } () } } func main() { ctx, cancel := (()) // Start a coroutine to execute tasks go worker(ctx) // After 5 seconds of execution, call cancel function to terminate the coroutine (5 * ) cancel() (2 * ) }
existmain
In the function, we useThe function creates a new
context
and pass it toworker
Functions, start coroutine running at the same timeworker
function.
whenworker
After the function is executed for 5 seconds, the main coroutine is calledcancel
Function to terminateworker
Coroutine. after,worker
Listen to cancel signals in coroutinesselect
The statement will be able to capture this signal and perform a terminated coroutine operation.
Finally, when exiting the coroutine,defer
Statements realize the release of resources. In summary, we have achieved elegant closure of coroutines and also recycled resources correctly.
4. Common scenarios where coroutines need to be actively shut down
4.1 Coroutines are performing a repetitive task
When a coroutine executes a repetitive task, the coroutine will not proactively terminate its operation. However, after a certain moment, there is no need to continue to perform the task, and it is necessary to actively close itgoroutine
The execution of the coroutine is released.
Hereetcd
Let’s explain it as an example.etcd
It is mainly used to store configuration information, metadata and some small-scale shared data in distributed systems. That is, we canetcd
Some key-value pairs are stored. So, if we want to set the validity period of key-value pairs, how should we achieve it?
etcd
There is one inleaseThe concept of , a lease can be regarded as a time period, in which the existence of a key-value pair is meaningful, but after the lease expires, the existence of the key-value pair is meaningless and can be deleted. At the same time, a lease can act on multiple key-value pairs. Here is an example of how to associate a lease with a key:
// client is the connection of etcd client, establish a Lease instance based on this// Lease example provides some APIs that can create a lease, cancel a lease, and renew a leaselease := (client) // Create a lease, and the lease time is 10 secondsgrantResp, err := ((), 10) if err != nil { (err) } // Lease ID, each lease has a unique IDleaseID := // Associate the lease with the key, the validity period of the key at this time, that is, the validity period of the lease_, err = ((), "key1", "value1", (leaseID)) if err != nil { (err) }
The above code demonstrates how toetcd
Create a lease in and associate it with a key-value pair. First, byetcd
The client's connection creates aLease
Instance, this instance provides a number of APIs that can create leases, cancel leases, and renew leases. Then useGrant
The function creates a lease and specifies that the lease has a validity period of 10 seconds. Next, get the lease ID, each lease has a unique ID. Finally, usePut
The function associates the lease with the key, thus setting the validity period of the key to the validity period of the lease.
So, if we want to operateetcd
The validity period of the middle key-value pair is only required to operate the validity period of the lease.
And just so,etcd
Actually defines aLease
Interface, This interface defines some operations on a lease, can create a lease, cancel the lease, and also supports renewal of the lease, obtaining expiration time and other contents. The details are as follows:
type Lease interface { // 1. Create a new lease Grant(ctx , ttl int64) (*LeaseGrantResponse, error) // 2. Cancel the lease Revoke(ctx , id LeaseID) (*LeaseRevokeResponse, error) // 3. Obtain the remaining validity period of the lease TimeToLive(ctx , id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) // 4. Obtain all leases Leases(ctx ) (*LeaseLeasesResponse, error) // 5. Continuously renew the lease. Here it is assumed that it expires after 10 seconds. The general meaning at this time is to renew the lease every 10 seconds. After calling this method, the lease will never expire. KeepAlive(ctx , id LeaseID) (<-chan *LeaseKeepAliveResponse, error) // 6. Renew the lease once KeepAliveOnce(ctx , id LeaseID) (*LeaseKeepAliveResponse, error) // 7. Close the Lease instance Close() error }
So far, we introduceLease
interface, andKeepAlive
The method is our protagonist today. From the definition of this method, it can be seen that when calledKeepAlive
Method After renewing a lease, it will perform the renewal of the target lease every once in a while. At this time, a coroutine is usually started, and the coroutine is used to complete the lease renewal operation.
At this time, the coroutine is actually executing a constantly repeating task, ifLease
The interface instance has been calledClose
Method, want to recycleLease
The instance will no longer operate on the lease and will be recycled.Lease
All the resources occupied, thenKeepAlive
The coroutine created by the method should also be closed actively at this time and should not continue to be executed.
In fact, currentlyetcd
middleLease
In the interfaceKeepAlive
The same is true for the default implementation of the method. And the implementation of the active shutdown of coroutine operation is also throughcontext
Pass the object,select
Get the cancel signal and finally passdefer
The combination of these three resources is achieved by recycling them.
Let’s take a look at the functions that perform contract renewal operations. A coroutine will be started and continuously executed in the background. The specific implementation is as follows:
func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) { for { var tosend []LeaseID now := () () // keepAlives saves all lease IDs to be renewed for id, ka := range { // Then nextKeepAlive is the next renewal time. If this time is exceeded, the renewal operation will be performed. if (now) { tosend = append(tosend, id) } } () // Send a renewal request for _, id := range tosend { r := &{ID: int64(id)} // Send a renewal request to the etcd cluster if err := (r); err != nil { return } } select { // Execute every 500ms case <-(500 * ): // If a termination signal is received, it will be terminated directly case <-(): return } } }
It can be seen that it will continue to cycle. First, it will check whether the current time exceeds the next renewal time of all leases. If it exceeds, the IDs of these leases will be placed in.tosend
In the array and in the next step of the loop toetcd
The cluster sends a renewal request. Then wait 500 milliseconds and then perform the above again. Under normal circumstances, it will not exit the loop and will continue toetcd
The cluster sends a renewal request. It will exit unless it receives a termination signal, thus ending the coroutine normally.
andstopCtx
Yeslessor
A variable of the instance, used to pass a cancel signal. Creatinglessor
When an instance,stopCtx
It is by()
Function created. This function returns two objects: one with a cancel methodObject (i.e.
stopCtx
), and a function objectstopCancel
, calling this function will cancel the context object. The details are as follows:
// Create a Lease instancefunc NewLeaseFromLeaseClient(remote , c *Client, keepAliveTimeout ) Lease { // ...Omit some irrelevant content reqLeaderCtx := WithRequireLeader(()) // Create cancelCtx object through the withCancel function , = (reqLeaderCtx) return l }
exist()
In the function, we callstopCancel()
Function to send a cancel signal.
func (l *lessor) Close() error { () // close for synchronous teardown if stream goroutines never launched // Omit irrelevant content return nil }
becausesendKeepAliveLoop()
The coroutine will bestopCtx
Waiting for signal, so once calledstopCancel()
, the coroutine will receive a signal and exit. This mechanism is very flexible becausestopCtx
is a member variable of the instance, solessor
All coroutines created by instances can be listened throughstopCtx
to decide whether to exit execution.
5. Summary
This article mainly introduces why you need to close it activelygoroutine
, and close in Gogoroutine
common routines.
The article first introduces why it is necessary to actively close itgoroutine
. Next, the article introduces in detailGo
Close in languagegoroutine
Common routines include passing termination signals and coroutine internal capture termination signals. In the scheme of delivering a termination signal, the article describes how to use itcontext
The object passes signals and usesselect
Statement waits for signal. In the scheme of capturing a termination signal inside the coroutine, the article describes how to use itdefer
Statements to recycle resources.
Finally, the article lists common scenarios where coroutines need to actively close the execution of coroutines. For example, coroutines are executing a constantly repetitive task and no longer need to continue execution, coroutines need to actively close the execution. I hope that through the introduction of this article, readers can understand how to close it at the right time.goroutine
, thereby avoiding the problem of waste of resources.
The above is the detailed content of how to elegantly close coroutine methods in go language development. For more information about go to close coroutines, please follow my other related articles!