concurrency limit and timeout control in Golang

Source: Internet
Author: User
Tags sprintf
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. goIt is then executed concurrently by the keyword, by channel returning the result.

channelAs 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 中读取数据

channelThere are two types of unbuffered (unbuffered) and buffer (buffered). For example, we created an unbuffered one just as follows channel .

ch := make(chan string)

channelBuffer, 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. goroutinewill 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.

chLimitThe 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? channelwithout 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

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.