This is a creation in Article, where the information may have evolved or changed. In [Rapidloop] (https://www.rapidloop.com/), we almost use [Go] (https://golang.org) to do everything, including our servers, application services and monitoring systems [Opsdash] (https ://www.opsdash.com/). Go is very good at writing asynchronous programs-the Goroutine and channel uses are simple and error-prone and are more powerful in terms of both syntax and functionality compared to other languages in asynchronous/wait mode. Read on to see some interesting Go codes around the task queue. # # Don't use task queues sometimes you don't need a task queue. Executing an asynchronous task can be like this: "Gogo process (Job)" is a good way for some requirements, such as sending an email while processing an HTTP request. The scale and complexity of the requirements determine whether we need a more granular infrastructure to handle tasks, and queue tasks to handle them in a controlled manner (for example, the number of tasks that control maximum parallelism). # # Simple Task queue Here is a simple queue and a process queue task worker function. Goroutine and channel are just the correct abstractions that encode them into elegant, compact blocks of code. ' Gofunc worker (Jobchan <-chan job) {for job: = Range Jobchan {process (Job)}}//make a channel with a capacity of 100. Jobchan: = Make (chan Job, +)//start the Workergo worker (Jobchan)//Enqueue a Jobjobchan <-Job "The above code creates a volume of 100 Job Object Channel. It then creates a Goruntine execution worker function. The worker pulls the task out of the channel and processes 1 tasks at a time. Tasks can be queued by pushing into the channel. Although only a few lines of code, but have done a lot of things. First, it's safe, right, and non-competitive without having to mix complex locks and thread code. Another function is the producer regulation. # # Producer regulation creates a channel with a volume of 100: ' go//make a channel with a capacity of 100.jobChAn: = Make (chan Job, 100) "Then insert the task into the queue: ' ' go//enqueue a jobjobchan <-job ' If there are 100 tasks that have not yet been processed, it will block in the channel. This is usually a good thing. If you have sla/qos restrictions or other hypothetical conditions (such as a task that takes a certain amount of time to complete), you certainly don't want to backlog too many tasks. If a task takes 1 seconds, it will take up to 100 seconds to complete your work. If the channel is full, you want your caller to be able to return after a certain period of time. For example: A REST API request, you can return a 503 error code and the caller can retry later. In this way, stress tests can be performed to ensure service quality. # # Non-blocking queue if you want to try to join the queue, when the need to block back to fail? This way you can get the failed status of the commit task back to 503. The key is to use the default statement for select: ' go//Tryenqueue tries to enqueue a job to the given job channel. Returns true if//The operation is successful, and false if enqueuing would not having been//possible without blocking. Job isn't enqueued in the latter Case.func tryenqueue (Job job, Jobchan <-chan job) bool {Select {case Jobchan <-Jo B:return Truedefault:return false}} "You can use this method to return to the failed state:" ' GOif! Tryenqueue (Job, chan) {http. Error (W, "Max capacity reached", 503) Return} ' # # Stop worker How can we gracefully stop the worker handler function? Assuming that we no longer insert a task into the queue and ensure that the tasks in all the queues can be processed, you only need to do this: "' Goclose (Jobchan)" Yes, just do it. It will work as expected because the ' for ... range ' Loop will eject the task: '' Gofor Job: = Range Jobchan {...} "And the loop exits when the channel is closed. All Tasks queued before the channel is closed are ejected normally and processed by the worker. # # Waiting for the worker to handle this may seem easy, but ' close (Jobchan) will not wait for Goroutine to complete and will exit. So we also need to use sync. Waitgroup: ' go//use a waitgroup var wg sync. Waitgroupfunc worker (Jobchan <-chan Job) {defer WG. Done () for job: = Range Jobchan {process (Job)}}//increment the waitgroup before starting the WORKERWG. ADD (1) Go Worker (Jobchan)//To stop the worker, first close the job Channelclose (Jobchan)//Then wait using the WAITGROUPWG . Wait () "So we can send the shutdown signal to the worker by closing the channel and use the WG." Wait waits until the worker processing is complete before exiting. Note: We must increment the wait group before starting Goroutine, and decrement it at the end of the goroutine, regardless of the way it is. # # Companion Timeout waiting ' WG. Wait () will wait until the goroutine exits. But what if we can't wait indefinitely? The following helper function encapsulates the ' WG. Wait () ' added time-out: ' ' go//waittimeout does a wait on a sync. Waitgroup object But with a specified//timeout. Returns true if the wait completed without timing out, false//otherwise.func waittimeout (WG *sync. Waitgroup, timeout time. Duration) BOOL {ch: = Make (chan struct{}) Go funC () {WG. Wait () Close (CH)} () Select {case <-ch:return truecase <-time. After (timeout): Return false}}//today use the waittimeout instead of WG. Wait () waittimeout (&WG, 5 * time. Second) "Now we wait for the worker to exit within a certain timeframe, and if the time limit is exceeded, it exits directly. # # Cancel worker Now we can get workers to finish their work even after we send a stop signal. But what should we do if we want the worker to abandon the current job and exit directly? We used the ' context. Context ': ' go//create a context that can be cancelledctx, cancel: = context. Withcancel (context. Background ())//Start the goroutine passing it the Contextgo worker (CTX, Jobchan) func worker (CTX context. Context, Jobchan <-chan Job) {for {select {case <-ctx. Done (): returncase Job: = <-jobchan:process (Job)}}}//Invoke Cancel when the worker is needs to be stopped. This *does not* wait//for the worker to Exit.cancel () "" In general, we created a "can cancel the context". The worker waits at the same time for this context and work queue, while ' CTX. Done () ' will be returned when the ' Cancel (') ' function is called. Similar to shutting down the task queue, the ' Cancel () ' function sends only the cancellation signal and does not wait for the cancel operation to complete. So if you need to wait for the worker to exit (even if the waiting time is very short and no other task needs to be performed), you must add the wait group code. But this piece of code is a bit more difficult. If you have a backlog of work in the channel (<-jobchanis not blocked), and the Cancel () function has been called (<-ctx. Done () is not blocked). Since two case is not blocked, ' select ' must make a choice between them. This does not actually happen. In spite of the ' <-ctx. Done () ' will also choose ' <-jobchan ' when it is not blocked seems reasonable, but in practice it is easy to fret. Even if we call the Cancel function, the channel will still eject the task, and if we insert more tasks, it will continue to run so incorrectly. But we don't need to worry, but be careful. The context cancellation case should have a higher priority than the other case. This is not easy, but using the built-in features of Go provides a solution to this problem. A parameter can be used to help us accomplish the purpose: "' Govar flag Uint64func Worker (CTX context. Context, Jobchan <-chan Job) {for {select {case <-ctx. Done (): returncase Job: = <-jobchan:process (Job) if atomic. LoadUint64 (&flag) = = 1 {return}}}}//Set the flag first, before cancellingatomic. StoreUint64 (&flag, 1) Cancel () "can also use the context's ' Err () ' function:" ' Gofunc worker (CTX context. Context, Jobchan <-chan Job) {for {select {case <-ctx. Done (): returncase Job: = <-jobchan:process (Job) if CTX. ERR ()! = nil {return}}}}cancel () "" We do not check ' flag/err () ' Before running the task because we want to finish processing the task before exiting after the task pops up. Of course, if you want the exit priority to be higher than the processing task, you can also check before processing. The bottom line is either to do some extra work before exiting the worker, or to carefully design the code to circumvent the flaw. # # Do not use the context to cancel worker ' context. Context ' does not apply to all situations, sometimes not using the context may make the code cleaner and clearer: "go//Create a Cancel Channelcancelchan: = Make (chan struct{})//start the goroutine passing It the Cancel channel go worker (Jobchan, Cancelchan) func worker (Jobchan <-chan Job, Cancelchan <-chan struct{}) {fo R {Select {case <-cancelchan:returncase job: = <-jobchan:process (Job)}}}//to cancel the worker, close the cancel Ch Annelclose (Cancelchan) "This is actually the context (simple, no hierarchy) cancel operation. Unfortunately, it also has the problems mentioned above. # # Worker Pool Finally, more than one worker can make the task work in parallel. The simplest way is to create multiple workers and get the task in the same task channel: "' Gofor i:=0; i<workercount; i++ {go worker (Jobchan)} "other code does not need to be modified. This allows multiple workers to attempt to read a task from the same channel. This is both effective and safe. Only one worker gets to the task, and the other worker blocks waiting for the task to arrive. This also has the problem of reasonable distribution. Imagine: With a total of 100 tasks and 4 workers, each worker should handle 25 tasks. But the fact is probably not, so the code should not be based on this assumption. To wait for the worker to exit, you can add the wait group: ' ' Gofor i:=0; i<workercount; i++ {WG. ADD (1) Go Worker (Jobchan)}//wait for all workers to EXITWG. Wait () "If you want to cancel the operation, you can create a cancel channel and close it will cancel all the workers." "' go//create cancel Channelcancelchan: = Make (chan struct{}//Pass the channel to the workers, let them wait on itfor i:=0; i<workercount; i++ {Go worker (Jobchan, Cancelchan)}//close the channel to signal the Workersclose (Cancelchan) "# # A common task Queue library on the surface, the task queue is very Simply, it can be packaged into a generic, reusable component. In fact, you often need to add more complex functionality to this generic component in different places. Plus it's easier to write a queue in Go than in any other language, so you can write your own queue where you need the queue. # # License above all code is published through the MIT Certificate: "Copyright (c) Rapidloop, Inc.permission is hereby granted, free of charge Obtaining a copyof This software and associated documentation files (the "Software"), to dealin the software without rest Riction, including without limitation the Rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or SELLC Opies of the software, and to permit persons to whom the software isfurnished to doing so, subject to the following condition S:the above copyright notice and this permission notice shall be included inall copies or substantial portions of the Soft Ware. The software is provided ' as is ', without WARRANTY of any KIND, EXPRESS orimpliED, including but isn't LIMITED to the warranties of merchantability,fitness for A particular PURPOSE and noninfringement. In NO EVENT shall theauthors or COPYRIGHT holders is liable for any CLAIM, damages OR otherliability, WHETHER in an ACTION of contract, TORT or OTHERWISE, arising from,out of or in CONNECTION with the software or the use or other dealings inthe Software. "
Via:https://www.opsdash.com/blog/job-queues-in-go.html
Author: Mahadevan Ramachandran Translator: Saberuster proofreading: Rxcai
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
1066 reads ∙1 likes