Task Queues in Go for Beginners: A Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Working with Task Queues in Go: For Beginners



Task queues are one of the fundamental patterns in modern development. In Go (Golang), this mechanism is implemented particularly elegantly thanks to built-in concurrency primitives: goroutines and channels. In this article, we will break down how to create task queues in Go, starting with simple examples and moving to advanced techniques.



What is a Task Queue and Why Do You Need It?



A task queue is a data structure that operates on the FIFO principle (First In, First Out). Imagine a queue at a checkout: the first customer is served, then the second, and so on. In programming, this allows you to:



  • Separate task submission from task processing
  • Limit the number of simultaneously executing operations
  • Distribute load evenly among handlers
  • Process tasks in the background without blocking the main thread


Go is ideally suited for implementing queues thanks to its concurrency model. Unlike many languages that require an external library (e.g., RabbitMQ or Redis), in Go you can create a simple task queue with just a few lines of code.



Creating a Simple Task Queue with Go Channels



The simplest way to implement a task queue is to use a buffered channel. Let's look at a basic example:



package main

import ( "fmt" "time")

func worker(tasks <-chan string, id int) { for task := range tasks { fmt.Printf("Worker %d started task: %s", id, task) time.Sleep(1 * time.Second) // Simulating work fmt.Printf("Worker %d completed task: %s", id, task) }}

func main() { tasks := make(chan string, 5) // Buffered channel for 5 tasks

// Start 2 workers for i := 1; i <= 2; i++ { go worker(tasks, i) }

// Send tasks to the queue for i := 1; i <= 10; i++ { tasks <- fmt.Sprintf("Task #%d", i) }

close(tasks) // Close the channel after sending all tasks time.Sleep(2 * time.Second) // Allow time for completion}


In this example:

  • The tasks channel acts as the queue
  • Two workers (goroutines) constantly listen to the channel and process tasks
  • A buffer size of 5 allows sending up to 5 tasks without immediate processing


Worker Pool Pattern: Scalable Processing



A Worker Pool is an evolution of a simple queue. Instead of a fixed number of workers, we create a pool that can be dynamically scaled. This pattern is especially useful for:



  • Processing HTTP requests
  • Batch data processing
  • Working with external APIs (limiting the number of concurrent requests)


Here is a more advanced implementation using a struct:



package main

import ( "fmt" "sync" "time")

type Task struct { ID int Payload string}

type WorkerPool struct { tasks chan Task results chan string wg sync.WaitGroup workers int}

func NewWorkerPool(workers int) *WorkerPool { return &WorkerPool{ tasks: make(chan Task, 100), results: make(chan string, 100), workers: workers, }}

func (wp *WorkerPool) Start() { for i := 1; i <= wp.workers; i++ { wp.wg.Add(1) go wp.worker(i) }}

func (wp *WorkerPool) worker(id int) { defer wp.wg.Done() for task := range wp.tasks { result := fmt.Sprintf("Worker %d executed task %d: %s", id, task.ID, task.Payload) time.Sleep(500 * time.Millisecond) // Simulating work wp.results <- result }}

func (wp *WorkerPool) AddTask(task Task) { wp.tasks <- task}\

Blogs

Book Recommendations