I. Introduction

Besides mutex (you can check my post about mutex here), waitGroup is a keyword that is also very common in golang programming. And like its name, it is for waiting! While mutex helps to lock a piece of code that you want only handled by one process at the same time, The waitGroup helps you force the one thread to stop and wait for the signal from the other thread (task) that it is done.
this will be very helpful in concurrency programming, especially for the shared memory technique.

II. Implementation

1. The Problem

Let’s say, If you need to block a thread, force it to wait until something is done, just remember those syntaxes:

var waitgroup = sync.WaitGroup{}
waitgroup.Add(1)
waitgroup.Done()
waitgroup.Wait()

Let’s have an example. You are calling 3 HTTP REST API, you want it to be executed concurrently, then handle the result:

package main

import (
	"io/ioutil"
	"net/http"
	"time"
)

var (
	fundInfo  []byte
	optInfo   []byte
	orderInfo []byte
)

func Get(url string) []byte {
	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	return body
}

func getFundInfo(url string) {
	fundInfo = Get(url)
}

func getOTP(url string) {
	optInfo = Get(url)
}

func getOrder(url string) {
	orderInfo = Get(url)

}

func isAbleToCharge() bool {
	// checking for fundInfo, orderInfo and OTPInfo here
	return false
}

func main() {
	go getFundInfo("https://.....?signature=xxx")
	go getOTP("https://.....?signature=yyy")
	go getOrder("https://.....?signature=zzz")

	println("Is able to charge: ", isAbleToCharge())
}

Now the problem comes: the main thread will call isAbleToChare() method before 3 HTTP API call is done (which is in need since they produce 3 important global variables).

2. The Solution

You may think: “OK, let me put the time.Sleep() to it and everything is OK”. Something like this

func main() {
	go getFundInfo("https://.....?signature=xxx")
	go getOTP("https://.....?signature=yyy")
	go getOrder("https://.....?signature=zzz")
    time.Sleep(3500 * time.Millisecond) // here
	println("Is able to charge: ", isAbleToCharge())
}

Honestly, this way sucks! No one does that in the real world. Instead, we could simply put waitGroup into it:

package main

import (
	"io/ioutil"
	"net/http"
	"sync"
)

var (
	waitGroup = sync.WaitGroup{}
	fundInfo  []byte
	optInfo   []byte
	orderInfo []byte
)

func Get(url string) []byte {
	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	return body
}

func getFundInfo(url string) {
	fundInfo = Get(url)
	waitGroup.Done() // signal that OK i'm done, this thread/task is completed
}

func getOTP(url string) {
	optInfo = Get(url)
	waitGroup.Done()
}

func getOrder(url string) {
	orderInfo = Get(url)
	waitGroup.Done()
}

func isAbleToCharge() bool {
	// checking for fundInfo, orderInfo and OTPInfo here
	return false
}

func main() {
	waitGroup.Add(3) // 3 thread that need to be waited, let Add 3

	go getFundInfo("https://.....?signature=xxx")
	go getOTP("https://.....?signature=yyy")
	go getOrder("https://.....?signature=zzz")

	waitGroup.Wait() // tell the main thread to wait
	println("Is able to charge: ", isAbleToCharge())
}

Now the main thread will wait till 3 API is called and responded. `isAbleToCharge()` method will no longer get the error.

III. Conclusion

  • We will digging deeper into waitGroup in the next part of this post, there are many exciting things!
  • You can get the code in my github repository

Leave a Reply

Your email address will not be published.