Using the Goroutines pool to handle task-based operations

Source: Internet
Author: User
* Author Note: After working with the Go language, I learned how to use the Unbuffered channel to build the Goroutines pool, which I like better than the way it shows in this article. That said, the article still has great value in the scenes it describes. * I was asked on multiple occasions why I use the working pool mode, why not start the required goroutines when needed? My answer has always been: limited by the type of work, the computing resources you have, and the platform you're on, blindly using goroutines will cause the program to run slowly, hurting the overall system's responsiveness and performance. Each program, system, and platform has a short board. Either memory, CPU, or bandwidth resources are not infinite. Therefore, it is very important for our program to reduce resource consumption and reuse limited resources. The work pool provides a pattern that helps programs manage resources and provides options to adjust resources. Show the principle of working pool:! [] (Https://raw.githubusercontent.com/studygolang/gctt-images/master/pool-go/1.png) as shown, the main business routine submits 100 tasks to the work pool. The work pool queues them up, and when a goroutine is idle, the work pool takes a task out of the task queue and assigns it to this goroutine, and the task will be executed. After execution, this goroutine will be idle again and wait for other tasks to be processed. The number of goroutines and the capacity of the queue are configurable, which means that the work pool can be used for program performance tuning. The Go language uses goroutine instead of threads. The Go run environment manages an internal thread pool and dispatches goroutines within the pool. The thread pool is a key tool to minimize the load on the Go run environment and maximize program performance. When we create a new goroutine, the Go runtime manages and dispatches the goroutine in the internal thread pool. This principle is the same as when the operating system dispatches threads on the idle CPU core. By Goroutine we can get the same effect as the dispatch thread pool, and maybe even better. I have a simple principle for handling task-based operations: less is more. I always want to know the minimum value of the goroutines required for the best results for a particular operation. The best outcome is not just how long it takes to complete a task, but also the impact that these tasks have on programs, systems, and platforms. You have to take into account both short-term and long-term effects. We can easily get very fast processing speed when the system or program load is light. ButA slight increase in system load one day will cause the previous configuration to have no effect, and we do not realize that we are seriously hurting the systems we interact with. We may have used the database or the Web server too hard, resulting in a system outage. A burst of 100 concurrent tasks can run normally, but a one-hour concurrency can be fatal. The work pool is not the Magic fairy that solves the world's computing problems, but it can be used to handle task-based operations in your program. It provides configuration options and control capabilities based on your system performance. As the system changes, you also have enough flexibility to change. Now let's take an example to prove that working pools are more efficient at handling task-based operations than blindly generating goroutines. Our test program runs a task that takes a MongoDB connection, executes a query command on the database, and returns data. Similar functionality will be found in the general business. This test program will submit 100 tasks to the working pool, running 5 times after the statistical average run time. Open the terminal and run the following command to download the code: "Export gopath= $HOME/examplego get Github.com/goinggo/workpooltestcd $HOME/example/bin" We create a working pool of 100 goroutines, which simulates blind models that produce the same number of goroutine based on the number of tasks. "./workpooltest the first parameter tells the program to create a working pool of 100 goroutines, and the second parameter tells the program to turn off verbose log output. On my Macbook, the result of running the above command is: "' cpu[8] routines[100] amountofwork[100] duration[4.599752] maxroutines[100] maxqueued[3] CPU[8] routines[100] amountofwork[100] duration[5.799874] maxroutines[100] maxqueued[3]cpu[8] Routines[100] AMOUNTOFWORK[100] duration[5.325222] maxroutines[100] maxqueued[3]cpu[8] routines[100] AmountOfWork[100] Duration[ 4.652793] maxroutines[100] maxqueued[3]cpu[8]ROUTINES[100] amountofwork[100] duration[4.552223] maxroutines[100] maxqueued[3]average[4.985973] ' ' Output parameter meaning: ' ' CPU[8]: The number of cores on my machineroutines[100]: the number of routines in the work poolamountofwork[100]: the N Umber of tasks to runduration[4.599752]: The amount of time in seconds the run tookmaxroutines[100]: the max number of R Outines that were active during the runmaxqueued[3]: the max number of tasks waiting in queued during the run ' now let's Run 6 4 goroutines Working pool: ' cpu[8] routines[64] amountofwork[100] duration[4.574367] maxroutines[64] maxqueued[35]cpu[8] ROUTINES[64] amountofwork[100] duration[4.549339] maxroutines[64] maxqueued[35]cpu[8] Routines[64] AmountOfWork[100] DURATION[4.483110] maxroutines[64] maxqueued[35]cpu[8] routines[64] amountofwork[100] Duration[4.595183] MaxRoutines [Maxqueued[35]cpu[8] routines[64] amountofwork[100] duration[4.579676] maxroutines[64] MaxQueued[35]Average[ 4.556335] "and then 24 goroutines results:" ' cpu[8 "routines[24]AMOUNTOFWORK[100] duration[4.595832] maxroutines[24] maxqueued[75]cpu[8] routines[24] AmountOfWork[100] Duration[ 4.430000] maxroutines[24] maxqueued[75]cpu[8] routines[24] amountofwork[100] duration[4.477544] MaxRoutines[24] MAXQUEUED[75]CPU[8] routines[24] amountofwork[100] duration[4.550768] maxroutines[24] MaxQueued[75]CPU[8] Routines[ [] amountofwork[100] duration[4.629989] maxroutines[24] maxqueued[75]average[4.536827] "' Finally 8 Goroutines: ' CPU[8 ] routines[8] amountofwork[100] duration[4.616843] maxroutines[8] maxqueued[91]cpu[8] routines[8] AmountOfWork[100] DURATION[4.477796] maxroutines[8] maxqueued[91]cpu[8] routines[8] amountofwork[100] duration[4.841476] MaxRoutines[8 ] maxqueued[91]cpu[8] routines[8] amountofwork[100] duration[4.906065] maxroutines[8] maxqueued[91]cpu[8] Routines[8] AMOUNTOFWORK[100] duration[5.035139] maxroutines[8] maxqueued[91]average[4.775464] "Let's collect a few of these results:" ' Go routines:4.985973:64 go routines:4.556335: ~430 Milliseconds Faster24 Go Routines:4.536827: ~450 Milliseconds Faster8 Go routines:4.775464: ~210 Milliseconds Faster ' ' The above test results tell us if a single core runs 3 Gorou Tines will get the best results. 3 seems to be a magical number, and this configuration will produce good results in every Go program I write. If we run a program with more cores, we can simply increase the number of goroutines to take full advantage of more resources and energy. This means that if MongoDB can handle more connections, then we can always adjust the size and capacity of the working pool to get the best results. We have proven that for specific operations, each task is blindly generated goroutines is not the best solution. Let's take a look at how the work Pool code works: The code for the work pool can be found in the code path you downloaded: ' CD $HOME/example/src/github.com/goinggo/workpool ' Workpool.go This file contains all the code. I removed all the comments and part of the code to exercise our focus on the important part. Let's start by looking at the type of build work pool: "' Gotype workpool struct {shutdownqueuechannel chan string Shutdownworkchannel chan struct{} Shutdownwaitgroup sync. Waitgroup Queuechannel Chan poolwork Workchannel Chan Poolworker queuedwork Int32 activeroutines Int32 queuecapacity int32 }type poolwork struct {work poolworker Resultchannel chan Error}type Poolworker interface {DoWork (workroutine int)} ' Wo Rkpool is the public type that represents the work pool. It achieves two channel. Workchannel is at the heart of the working pool, which manages the work queue that needs to be processed. All goroutines will wait for the channel signal. Queuechannel is used to manage submission work to Workchannel. QueueThe Channel provides the caller with confirmation that the work is entering the queue and is responsible for maintaining both the queuedwork and queuedcapacity counters. The poolwork struct defines the data sent to Queuechannel for processing incoming queue requests. It contains the interface that involves the user Poolworker object and a channel that receives a confirmation that the task has entered the queue. The Poolworker interface defines the DoWork function, where one parameter represents the internal ID of the goroutines that runs the task. This ID is useful for logging and other Goroutines-level transactions. The Poolworker interface is the core of the work pool that is used to receive and run tasks. Let's look at a simple client implementation: "' Gotype mytask struct {Name string WP *workpool. Workpool}func (Mt *mytask) DoWork (workroutine int) {FMT. PRINTLN (Mt. Name) fmt. Printf ("*******> WR:%d QW:%d AR:%d\n", Workroutine, Mt. Wp. Queuedwork (), Mt. Wp. Activeroutines ()) time. Sleep (Time.millisecond)}func main () {runtime. Gomaxprocs (runtime. NUMCPU ()) Workpool: = Workpool. New (runtime. NUMCPU () * 3, +) Task: = mytask{Name: "A" + StrConv. Itoa (i), Wp:workpool,} ERR: = Workpool.postwork ("main", &task) ...} "I created a mytask type that defines the state of the work execution. Then I implement a MyTask function member DoWork, which conforms to the signature of the Poolworker interface. Because MyTask implements the Poolworker interface, objects of the MyTask type are also considered objects of the Poolworker type. Now let's pass an object of type MyTask into the Postwork method. To learn more about interface and object-based programming in the Go language, you canThe following link: https://www.ardanlabs.com/blog/2013/07/object-oriented-programming-in-go.html I set the go run environment to use all the CPUs and cores on my machine, I created a working pool of 24 goroutines. I have 8 cores on this machine, as we have concluded above, each core allocation of 3 Goroutines is a better configuration. The last parameter is to tell the work pool to create a queue of 100 tasks. I then created a MyTask object and committed it to the queue. In order to log, the first parameter of the Postwork method can be set to the name of the caller. If the err parameter returned by the call is NULL, it indicates that the task has been committed, and if not NULL, the approximate rate means that the capacity of the queue has been exceeded and your task could not be submitted. Let's look inside the code to see how the Workpool object was created and started: "Gofunc New (numberofroutines int, queuecapacity int32) *workpool {workpool = workpool{ Shutdownqueuechannel:make (Chan string), Shutdownworkchannel:make (Chan struct{}), Queuechannel:make (Chan poolwork), Workchannel:make (Chan Poolworker, queuecapacity), queuedwork:0, activeroutines:0, Queuecapacity:queuecapacity,} for W Orkroutine: = 0; Workroutine < Numberofroutines; workroutine++ {workPool.shutdownWaitGroup.Add (1) Go Workpool.workroutine (workroutine)} Go Workpool.queueroutine () Return &workpool} "We see the number of Goroutines in the client sample code above and the queue length passed into the New function. Workchannel is a buffer channel that is used to store the queue of work that needs to be processed. QueuechThe Annel is a non-buffered channel used to synchronize access to the Workchannel buffer and maintain counters. To learn more about buffering and non-buffered channel, please visit this link: http://golang.org/doc/effective_go.html#channels when the channel is initialized, we can create It's goroutines. First we turn off each goroutine waitgroup plus one. Then create the Goroutines. Finally open Queueroutine to receive work. To learn how to turn off Goroutines's code and how Waitgroup works, read this link: Http://dave.cheney.net/2013/04/30/curious-channels the implementation of the close work pool is as follows: " Gofunc (WP *workpool) Shutdown (Goroutine string) {Wp.shutdownqueuechannel <-"down" <-wp.sutdownqueuechannel Close (Wp.queuechannel) Close (wp.shutdownqueuechannel) Close (Wp.shutdownworkchannel) wp.shutdownWaitGroup.Wait () The Close (wp.workchannel)} ' shutdown function first closes queueroutine so that no more requests are received. Then close the Shutdownworkchannel and wait for each goroutine to do a minus operation on the Waitgroup counter. Once the last goroutine invokes the done function, wait for the function to be returned and the working pool will be closed. Now let's look at the Postwork and Queueroutine functions: "Gofunc (WP *workpool) postwork (Goroutine string, work Poolworker) (err error) {Pool Work: = Poolwork{work, make (chan error)} defer close (poolwork.resultchannel) Wp.queuechannel <-poolwork retUrn <-poolwork.resultchannel} "" Gofunc (WP *workpool) queueroutine () {for {select {case <-wp.shutdownqueuecha Nnel:wp.shutdownQueueChannel <-' down ' return case queueitem: = <-wp.queuechannel:if atomic. AddInt32 (&wp.queuedwork, 0) = = wp.queuecapacity {queueitem.resultchannel <-fmt. Errorf ("Thread Pool at Capacity") continue} atomic. AddInt32 (&wp.queuedwork, 1) wp.workchannel <-queueitem.work queueitem.resultchannel <-Nil Break}}} " The idea behind the postwork and Queueroutine functions is to serialize access to the Workchannel buffers, guaranteeing queue order and maintaining counters. When the work is submitted to the channel, the GO run environment guarantees that it will always be placed at the end of the Workchannel. When the Queuechannel receives the signal, Queueroutine will receive a job. The code first checks if the queue is still empty, and if the object with Poolworker is queued to the Workchannel buffer. When all transactions are queued, the caller gets the result returned. Let's take a look at the function of Workroutine: "Gofunc (WP *workpool) workroutine (workroutine int) {for {select {case <-wp.shutdownworkcha Nnel:wp.shutdownWaitGroup.Done () return case Poolworker: = <-wp.workchannel:wp.safelydowork (Workroutine, poolworker) Break}} "" Gofunc (WP *workpool) Safelydowork (workroutine int, poolworker poolworker) {defer catchpanic (nil, "Workroutine", "Workpool"). Workpool "," Safelydowork ") defer atomic. AddInt32 (&wp.activeroutines,-1) atomic. AddInt32 (&wp.queuedwork,-1) atomic. AddInt32 (&wp.activeroutines, 1) poolworker.dowork (workroutine)} ' Go run environment by Goroutine to idle Workchannel Send a signal to assign work to Goroutine. When the channel receives a signal, the GO runtime environment will pass the first task of the channel buffer to Goroutine to handle. This channel's buffer is like a first-in, first-out queue. If all the goroutines are busy, all the rest of the work will have to wait. As soon as a routine completes its assigned work, it returns and continues to wait for workchannel notifications. If the channel buffers are working, then the GO runtime will wake up the goroutine. The code uses the Safelydo pattern because the code calls code in user mode, there is a possibility of a crash, and you certainly don't want Goroutine to stop working together. Note that the first defer declaration, which will catch any crashes, keeps the code running continuously. Other parts of the code will safely increase or decrease the counter, invoking the part of user mode through the interface. To learn more about capturing crashes, read the following article: https://www.ardanlabs.com/blog/2013/06/ Understanding-defer-panic-and-recover.html This is the core of the code and how it implements such a pattern. Workpool elegant display of the use of the channel. I can use a very small amount of code to handle the work. Increasing the queue's guarantee mechanism and the maintenance of the counters is a piece of cake. Please download the code from Goinggo's code warehouse and try it out for yourself.

Via:https://www.ardanlabs.com/blog/2013/09/pool-go-routines-to-process-task.html

Author: William Kennedy Translator: lebai03 proofreading: polaris1119

This article by GCTT original compilation, go language Chinese network honor launches

This article was originally translated by GCTT and the Go Language Chinese network. Also want to join the ranks of translators, for open source to do some of their own contribution? Welcome to join Gctt!
Translation work and translations are published only for the purpose of learning and communication, translation work in accordance with the provisions of the CC-BY-NC-SA agreement, if our work has violated your interests, please contact us promptly.
Welcome to the CC-BY-NC-SA agreement, please mark and keep the original/translation link and author/translator information in the text.
The article only represents the author's knowledge and views, if there are different points of view, please line up downstairs to spit groove

187 Reads
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.