How do I make this program thread-safe, would channels be the best implementation, if so, how?


I’m using Golang, I’m trying to make this program thread-safe. It takes a number as a parameter (which is the number of consumer tasks to start), reads lines from an input, and accumulates word count. I want the threads to be safe (but I don’t want it to just lock everything, it needs to be efficient) should I use channels? How do I do this?

package main

import (

// Consumer task to operate on queue
func consumer_task(task_num int) {
    fmt.Printf("I'm consumer task #%v ", task_num)
    fmt.Println("Line being popped off queue: " + queue[0])

    queue = queue[1:]


// Initialize queue
var queue = make([]string, 0)

func main() {

    // Initialize wait group 
    var wg sync.WaitGroup

    // Get number of tasks to run from user
    var numof_tasks int
    fmt.Print("Enter number of tasks to run: ")
    // Open file
    file, err := os.Open("test.txt")
    if err != nil {

    defer file.Close()

    // Scanner to scan the file
    scanner := bufio.NewScanner(file)
    if err := scanner.Err(); err != nil {

    // Loop through each line in the file and append it to the queue
    for scanner.Scan() {
        line := scanner.Text()  
        queue = append(queue, line)

    // Start specified # of consumer tasks
    for i := 1; i <= numof_tasks; i++ {
        go func(i int) { 

    fmt.Println("All done")


You have a data race on the slice queue. Concurrent goroutines, when popping elements off the head of the queue to do so in a controlled manner either via a sync.Mutex lock. Or use a channel to manage the "queue" of work items.

To convert what you have to using channels, update the worker to take an input channel as your queue – and range on the channel, so each worker can handle more than one task:

func consumer_task(task_num int, ch <-chan string) {

    fmt.Printf("I'm consumer task #%v\n", task_num)
    for item := range ch {
        fmt.Printf("task %d consuming: Line item: %v\n", task_num, item)
    // each worker will drop out of their loop when channel is closed

change queue from a slice to a channel & feed items in like so:

queue := make(chan string)

go func() {
    // Loop through each line in the file and append it to the queue
    for scanner.Scan() {
        queue <- scanner.Text()
    close(queue) // signal to workers that there is no more items

then just update your work dispatcher code to add the channel input:

go func(i int) {
    consumer_task(i, queue) // add the queue parameter

Answered By – colm.anseo

Answer Checked By – Jay B. (GoLangFix Admin)

Leave a Reply

Your email address will not be published.