This is a creation in Article, where the information may have evolved or changed.
Objective
Last time in the Go write a Lightweight SSH batch operation tool mentioned, we do golang concurrency to limit concurrency, the execution of Goroutine to have time-out control. That will not be discussed here.
The following sample code can all run tests directly on the Go Playground:
Concurrent
Let's go ahead and run a simple concurrency look
package mainimport ( "fmt" "time")func run(task_id, sleeptime int, ch chan string) { time.Sleep(time.Duration(sleeptime) * time.Second) ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime) return}func main() { input := []int{3, 2, 1} ch := make(chan string) startTime := time.Now() fmt.Println("Multirun start") for i, sleeptime := range input { go run(i, sleeptime, ch) } for range input { fmt.Println(<-ch) } endTime := time.Now() fmt.Printf("Multissh finished. Process time %s. Number of tasks is %d", endTime.Sub(startTime), len(input))}
run()
the function accepts input parameters for sleep
several seconds. go
It is then executed concurrently by the keyword, by channel
returning the result.
channel
As the name implies, he is the goroutine
"conduit" between communication. The flow of data in a pipeline is, in fact, goroutine
a memory share. We pass the data through which he can goroutine
interact.
ch <- xxx // 向 channel 写入数据<- ch // 从 channel 中读取数据
channel
There are two types of unbuffered (unbuffered) and buffer (buffered). For example, we created an unbuffered one just as follows channel
.
ch := make(chan string)
channel
Buffer, we'll talk about it for a moment, just look at the results of the implementation.
Multirun starttask id 2 , sleep 1 secondtask id 1 , sleep 2 secondtask id 0 , sleep 3 secondMultissh finished. Process time 3s. Number of tasks is 3Program exited.
Three goroutine
' sleep 3,2,1 seconds respectively. But it takes only 3 seconds to complete. So concurrency takes effect, go concurrency is that simple.
return by order
Just in the example, the order in which I perform the task is 0,1,2. But the channel
order returned from the IS 2,1,0. This is very well understood, because task 2 executes the fastest, so first returns the entry channel
, Task 1 times, Task 0 slowest.
What if we want to return the data in the order in which the tasks are executed? It can be channel
done by an array (well, it should be called a slice), like this.
package mainimport ( "fmt" "time")func run(task_id, sleeptime int, ch chan string) { time.Sleep(time.Duration(sleeptime) * time.Second) ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime) return}func main() { input := []int{3, 2, 1} chs := make([]chan string, len(input)) startTime := time.Now() fmt.Println("Multirun start") for i, sleeptime := range input { chs[i] = make(chan string) go run(i, sleeptime, chs[i]) } for _, ch := range chs { fmt.Println(<-ch) } endTime := time.Now() fmt.Printf("Multissh finished. Process time %s. Number of tasks is %d", endTime.Sub(startTime), len(input))}
Running results, the output is now in the same order as the input order.
Multirun starttask id 0 , sleep 3 secondtask id 1 , sleep 2 secondtask id 2 , sleep 1 secondMultissh finished. Process time 3s. Number of tasks is 3Program exited.
Timeout control
We did not consider the timeout in the example just now. However, if a certain goroutine
run time is too long, it will certainly drag the main goroutine
block, the entire program hangs there. So we need to have time-out control.
Usually we can do select
a time.After
timeout check through +, for example, we add a function Run()
to Run()
execute in go run()
. and select
time.After
The timeout is determined by +.
Package Mainimport ("FMT" "Time") func-Run (task_id, sleeptime, timeout int, ch Chan string) {ch_run: = Make (chan String) go Run (task_id, Sleeptime, Ch_run) Select {case Re: = <-ch_run:ch <-re case <-time . After (time. Duration (Timeout) * time. Second): Re: = FMT. SPRINTF ("Task ID%d, timeout", task_id) Ch <-re}}func run (task_id, sleeptime int, ch Chan string) {Tim E.sleep (time. Duration (sleeptime) * time. Second) Ch <-fmt. SPRINTF ("task ID%d, sleep%d second", task_id, Sleeptime) Return}func main () {input: = []int{3, 2, 1} timeout : = 2 CHS: = Make ([]chan string, Len (input)) StartTime: = time. Now () Fmt. Println ("Multirun start") for I, sleeptime: = Range input {chs[i] = make (Chan string) go Run (i, Sleeptim E, timeout, chs[i])} for _, ch: = Range CHS {FMT. Println (<-ch)} endTime: = time. Now () Fmt. Printf ("Multissh finished. Process time%s. Number of task is%d", Endtime.sub (StartTime), Len (input)}
Run result, task 0 and Task 1 already timed out
Multirun starttask id 0 , timeouttask id 1 , timeouttasi id 2 , sleep 1 secondMultissh finished. Process time 2s. Number of task is 3Program exited.
Concurrency limit
If the number of tasks is too large and unrestricted concurrency is turned on goroutine
, it may be too much resource-intensive and the server may explode. Therefore, the concurrency limit in the real environment is also certain to be done.
A common practice is to use channel
the buffering mechanism-the one we mentioned at the beginning.
We create a separate, buffered and non-buffered channel
look
The difference between the two is that if channel
there is no buffer, or the buffer is full. goroutine
will automatically block until channel
the data is read away. As an example,
package mainimport ( "fmt")func main() { ch := make(chan string) ch <- "123" fmt.Println(<-ch)}
This code execution will error
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main() /tmp/sandbox531498664/main.go:9 +0x60Program exited.
This is because we are creating ch
an unbuffered one channel
. So in the execution ch<-"123"
, this goroutine
is blocked, and fmt.Println(<-ch)
there is no way to get it done in the back. So the error will be reported deadlock
.
If we change this, the program can execute
package mainimport ( "fmt")func main() { ch := make(chan string, 1) ch <- "123" fmt.Println(<-ch)}
Perform
123Program exited.
If we change to this,
package mainimport ( "fmt")func main() { ch := make(chan string, 1) ch <- "123" ch <- "123" fmt.Println(<-ch) fmt.Println(<-ch)}
Although the channel was read two times, the program is still deadlocked because the buffer is full and the goroutine
blocking is suspended. The second one ch<- "123"
is no way to write.
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main() /tmp/sandbox642690323/main.go:10 +0x80Program exited.
Therefore, by using the buffer settings of the channel, we can implement the concurrency limit. We just have to write something in a buffer channel
(whatever it is, it doesn't matter) while executing concurrency. Let's goroutine
read the contents of this in parallel after the execution is done channel
. This way the entire number of concurrent quantities is controlled in this channel
buffer size.
For example, we can use a bool
type of buffer channel
as the concurrency limit counter.
chLimit := make(chan bool, 1)
And then, in the concurrent execution, every new goroutine is created, and everything is chLimit
plugged in.
for i, sleeptime := range input { chs[i] = make(chan string, 1) chLimit <- true go limitFunc(chLimit, chs[i], i, sleeptime, timeout) }
The go
new constructed function is executed here by the keyword concurrency. After he executes the original Run()
, he will chLimit
consume one of the buffers.
limitFunc := func(chLimit chan bool, ch chan string, task_id, sleeptime, timeout int) { Run(task_id, sleeptime, timeout, ch) <-chLimit }
This way, when the goroutine
number of created reaches chLimit
the upper limit of the buffer. The master goroutine
hangs up and blocks until the goroutine
execution is completed and the chLimit
data in the buffer is consumed, and the program continues to create the new goroutine
. The purpose of our concurrent quantitative limits is also achieved.
The following is the complete code
Package Mainimport ("FMT" "Time") func-Run (task_id, sleeptime, timeout int, ch Chan string) {ch_run: = Make (chan String) go Run (task_id, Sleeptime, Ch_run) Select {case Re: = <-ch_run:ch <-re case <-time . After (time. Duration (Timeout) * time. Second): Re: = FMT. SPRINTF ("Task ID%d, timeout", task_id) Ch <-re}}func run (task_id, sleeptime int, ch Chan string) {Tim E.sleep (time. Duration (sleeptime) * time. Second) Ch <-fmt. SPRINTF ("task ID%d, sleep%d second", task_id, Sleeptime) Return}func main () {input: = []int{3, 2, 1} timeout : = 2 Chlimit: = Make (chan bool, 1) CHS: = Make ([]chan string, Len (input)) Limitfunc: = func (Chlimit chan bool, CH Chan string, task_id, sleeptime, timeout int) {Run (task_id, sleeptime, timeout, ch) <-chlimit} s Tarttime: = time. Now () Fmt. Println ("Multirun start") for I, sleeptime: = Range input {chs[i] = make (Chan string, 1) ChliMIT <-True Go Limitfunc (Chlimit, chs[i], I, Sleeptime, timeout)} for _, ch: = Range CHS {FMT. Println (<-ch)} endTime: = time. Now () Fmt. Printf ("Multissh finished. Process time%s. Number of task is%d ", Endtime.sub (StartTime), Len (input)}
Run results
Multirun starttask id 0 , timeouttask id 1 , timeouttask id 2 , sleep 1 secondMultissh finished. Process time 5s. Number of task is 3Program exited.
chLimit
The buffer is 1. Task 0 and Task 1 time out for 2 seconds. Task 2 takes 1 seconds. The total time is 5 seconds. Concurrency restrictions are in effect.
If we modify the concurrency limit to 2
chLimit := make(chan bool, 2)
Run results
Multirun starttask id 0 , timeouttask id 1 , timeouttask id 2 , sleep 1 secondMultissh finished. Process time 3s. Number of task is 3Program exited.
Task 0, Task 1 executes concurrently and takes 2 seconds. Task 2 takes 1 seconds. The total time is 3 seconds. Meet expectations.
Have you noticed that there is a place in the code that is different from before. Here, with a bufferedchannel
chs[i] = make(chan string, 1)
Do you remember the example above? channel
without buffering, this will be blocked until he is consumed goroutine
.
However, if the concurrency limit here, that is, chLimit
blocking the main goroutine
, then the code behind the consumption of this data will not be executed to ... So deadlock
pull it!
for _, ch := range chs { fmt.Println(<-ch) }
So give him a cushion just fine.
Reference documents
Understanding the Go channel mechanism from deadlock error (i)
Golang-what-is-channel-buffer-size
Golang-using-timeouts-with-channels
Above
Reprint Authorization
CC By-sa