This is a creation in Article, where the information may have evolved or changed.
There are three classic ways to control concurrency, one is to implement concurrency control through channel notifications, and the other is the context, Waitgroup.
1. Implementing concurrency control using the most basic channel notification
No buffer channel
Unbuffered channels refer to the size of the channel 0, meaning that this type of channel does not have the ability to hold any value before it is received, and it requires goroutine both sending and receiving to goroutine be ready to complete the send and receive operations.
From the non-buffered channel definition above, the send goroutine and receive gouroutine must be synchronous, and when prepared, the first action will block the wait until another corresponding operation is ready. This unbuffered channel is also known as a synchronous channel.
Formally implement multi-concurrency control via unbuffered channels goroutine
func main() { ch := make(chan instruct{}) go func() { ch <- struct{}{} }() fmt.Println(<-ch)}
When the master goroutine runs to the <-ch accepted channel value, if channel there is no data in it, the wait is blocked until there is a value. This makes it easy to implement concurrency control
2. Implementing concurrency control via the Waitgroup in the Sync pack
In the sync package, provided WaitGroup , it waits for all the tasks it collects to goroutine complete, goroutine Add(delta int) asking for the number of Waits goroutine in the master. After each completion, goroutine it is Done() indicated that this one goroutine has been completed, and when all is done, returns are returned goroutine in the master goroutine WaitGroup .
fun main(){ var wg sync.WaitGroup var urls = []string{ "http://www.golang.org/", "http://www.google.com/", "http://www.somestupidname.com/", } for _, url := range urls { wg.Add(1) go func(url string) { defer wg.Done() http.Get(url) }(url) } wg.Wait()}
But in the Golang official website, there is such a sentence
- A Waitgroup must not being copied after first use.
The translation is enough to come, WaitGroup after the first use, cannot be copied, because there will be a problem
func main() { wg := sync.WaitGroup{} for i := 0; i < 5; i++ { wg.Add(1) go func(wg sync.WaitGroup, i int) { log.Printf("i:%d", i) wg.Done() }(wg, i) } wg.Wait() log.Println("exit")}
The operation results are as follows
2009/11/10 23:00:00 i:42009/11/10 23:00:00 i:02009/11/10 23:00:00 i:12009/11/10 23:00:00 i:22009/11/10 23:00:00 i:3fatal error: all goroutines are asleep - deadlock!goroutine 1 [semacquire]:sync.runtime_Semacquire(0x1040a13c, 0x44bc) /usr/local/go/src/runtime/sema.go:47 +0x40sync.(*WaitGroup).Wait(0x1040a130, 0x121460) /usr/local/go/src/sync/waitgroup.go:131 +0x80main.main() /tmp/sandbox894380819/main.go:19 +0x120
It prompts me that all of goroutine them have slept, and there is a deadlock. This is because the wg copy is passed to the goroutine middle, resulting in only the Add operation, in fact the Done operation is wg executed in the copy. So Wait it's dead locked.
Four types of references in Go are slice, channel, function, map
Correction Method One:
Change the incoming type in the anonymous function wg *sync.WaitGrou so that you can refer to the correct waitgroup.
Correction Method Two:
Remove the incoming parameters from the anonymous function wg because go supports the closure type, and the outside variables can be used directly in the anonymous function wg
3. Powerful context context introduced after go 1.7 for concurrency control
3.1 Introduction
It is enough to use in some simple scenarios channel WaitGroup , but when faced with some complex and changeable network concurrency scenarios channel and WaitGroup seems a little overwhelmed. For example, a network request Request , each Request need to open one to goroutine do something, which goroutine may open other goroutine , such as the database and RPC services. So we need a way to keep track of what we can do to goroutine control them, and that's what the go language provides for us Context , called context, which is goroutine the context. It is a program that includes a running environment, a scene, and snapshots. Every time a program is run, it needs to know the current program's running state, usually go wraps it in one Context , and then passes it to the execution goroutine . contextpackages are primarily used to deal with multiple goroutine shared data, and multiple goroutine management.
3.2 Package Context
contextThe core of the package is struct Context that the interface is declared as follows:
//a Context carries a deadline, cancelation signal, and request-scoped values//across API Boundari Es. Its methods is safe for simultaneous use by multiple//Goroutines.type Context Interface {//Done returns a channel T Hat is closed if this ' Context ' is canceled//or times out. Done () <-chan struct{}//ERR indicates why this Context was canceled, after the Do channel//is closed. ERR () error//Deadline returns the time when this Context would be is canceled, if any. Deadline () (Deadline time. Time, OK bool)//value returns the value associated with key or nil if none. Value (Key interface{}) interface{}}
Done()Returns a type that accepts only data channel , and when the context is closed or the time- out expires , the channel has a cancellation signal
Err()Done()after that, the context reason for the cancellation is returned.
Deadline()Set the context cancel point in time
Value()Method allows an Context object to carry scoped request data, which must be thread-safe.
ContextThe object is thread-safe, and you can Context pass an object to any number gorotuine , and all goroutine will receive a cancellation signal when it cancels the operation.
One Context cannot have a Cancel method, and we can only Done channel receive data.
The reason behind this is consistent: the function that receives the cancellation signal and the function that sends the signal is usually not one.
A typical scenario is that the parent action starts as a child action operation goroutine and the child action cannot cancel the parent operation.
3.3 Inheriting the context
The context package provides functions to assist users in Context creating new objects from existing objects Context .
These Context objects form a tree: When an Context object is canceled, all inherited from it Context will be canceled.
Backgroundis Context the root of all object trees and it cannot be canceled. It declares the following:
// Background returns an empty Context. It is never canceled, has no deadline,// and has no values. Background is typically used in main, init, and tests,// and as the top-level `Context` for incoming requests.func Background() Context
WithCanceland WithTimeout functions return inherited Context objects that can be canceled earlier than their parent Context .
When the request handler function returns, the request is associated with a Context cancellation. When you use multiple replicas to send a request, you can use WithCancel the Cancel extra request. WithTimeoutuseful when setting a time-out for back-end server requests. Here are the declarations of these three functions:
// WithCancel returns a copy of parent whose Done channel is closed as soon as// parent.Done is closed or cancel is called.func WithCancel(parent Context) (ctx Context, cancel CancelFunc)// A CancelFunc cancels a Context.type CancelFunc func()// WithTimeout returns a copy of parent whose Done channel is closed as soon as// parent.Done is closed, cancel is called, or timeout elapses. The new// Context's Deadline is the sooner of now+timeout and the parent's deadline, if// any. If the timer is still running, the cancel function releases its// resources.func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithValueThe function can relate the data of the request scope to the Context object. The statement reads as follows:
// WithValue returns a copy of parent whose Value method returns val for key.func WithValue(parent Context, key interface{}, val interface{}) Context
3.4 Context Example
Of course, to know how the Context package works, the best way is to look at an example.
func childfunc (cont context. Context, num *int) {CTX, _: = Context. Withcancel (cont) for {select {case <-ctx. Done (): FMT. PRINTLN ("Child-Done:", ctx.) ERR ())) return}}}func main () {gen: = Func (CTX context. Context) <-chan int {dst: = make (chan int) N: = 1 Go func () {for {sel ECT {case <-ctx. Done (): FMT. Println ("Parent Done:", ctx.) ERR ()) return//Returning leak the Goroutine case DST <-N: n++ Go Childfunc (CTX, &n)}} () return DST} CTX, Cancel: = context. Withcancel (context. Background ()) for n: = Range Gen (ctx) {fmt. PRINTLN (n) if n >= 5 {Break}} cancel () time. Sleep (5 * time. Second)}
In the example above, the main description is to channel implement a loop with a loop number of 5,
In each loop, one is generated, goroutine each goroutine is passed in context , goroutine a child ctx Context is created by passing in each, and the select running condition is monitored by the Context parent Context When exiting, the code does not clearly call the sub Context - Cancel function, but the analysis results, the child Context is properly closed, this is because, all based on this Context or derivative of the child Context will be notified, then can be cleaned up the operation, Eventually released goroutine , this elegantly solves the goroutine problem of uncontrolled startup.
Here is the result of the operation:
Picture. png
3.5 Context Usage Principles
- do not place the
context in the struct, and you want to pass the
- context
function method as a parameter by passing the context As the first parameter, place the first bit.
- When passing
context to a function method, do not pass nil and use Context if you do not know what to pass. TODO -
context Value related methods should pass the necessary data, and do not use any data for this pass -
Context is thread-safe and can be safely passed in multiple goroutine