SoFunction
Updated on 2025-03-05

Go uses coroutines to obtain data in batches to speed up interface return speed

Recommend go learning books, click the link to jump to the official JD mall to purchase.

The server often needs to return a list that contains a lot of user data. The conventional approach is of course to traverse and then read the cache.

After using Go, it can be obtained concurrently, greatly improving efficiency.

Using channel

Copy Highlighter-hljs

package main

import (
   "fmt"
   "time"
)

func add2(a, b int, ch chan int) {
   c := a + b
   ("%d + %d = %d\n", a, b, c)
   ch <- 1 //After the execution, write a message that you have completed it}

func main() {
   start := ()
   chs := make([]chan int, 10)
   for i := 0; i < 10; i++ {
      chs[i] = make(chan int)
      go add2(1, i, chs[i]) //Add 10 coroutines have been allocated   }
   for _, ch := range chs {
      <-ch //Wait for loop, you have to complete each time before you can continue, otherwise you will wait   }
   end := ()
   consume := (start).Seconds()
   ("Time-consuming program execution(s):", consume)
}

After the business logic of each coroutine's add() function is completed, we send a data to the corresponding channel through the ch <- 1 statement.

After all coroutines are started, we receive data from the channel slice chs in sequence through the <-ch statement (no processing of the result is equivalent to the data written to the channel being just an identifier, indicating that the coroutine logic to which this channel belongs has been executed).

Until all channel data is received, then printing the main program takes time and exits.

Using WaitGroup

  • Add: The WaitGroup type has a counter, and the default value is 0. We can increase the value of this counter through the Add method. Usually, we can use a method to mark the number of child coroutines that need to be waited for;
  • Done: When a child coroutine is executed, it can be marked as completed by the Done method, which will decrement the counter value of the WaitGroup type instance to which it belongs, and it can usually be called through the defer statement;
  • Wait: The function of the Wait method is to block the current coroutine until the counter value of the corresponding WaitGroup type instance reaches zero. If the value of the corresponding counter is already 0 when the method is called, then it will not do anything.
package main

import (
   "fmt"
   "sync"
)

func addNum(a, b int, deferFunc func()) {
   defer func() {
      deferFunc()
   }()
   c := a + b
   ("%d + %d = %d\n", a, b, c)
}

func main() {
   var wg 
   (10) //Equivalent to 10 tokens issued   for i := 0; i &lt; 10; i++ {
      go addNum(i, 1, ) //Every execution consumes tokens   }
   () //Waiting for the token to be consumed}

It should be noted that the counter of this type cannot be less than 0, otherwise the following panic will be thrown:

panic: sync: negative WaitGroup counter

Apply to practice

func GetManyBase(userIds []int64) []UserBase {
   userCaches := make([]UserBase, len(userIds))

   var wg 
   for index, userId := range userIds {
      (1)
      go func(index int, userId int64, userCaches []UserBase) {
         userCaches[index] = NewUserCache(userId).GetBase() 
         ()
      }(index, userId, userCaches)
   }
   ()

   return userCaches
}

There are two problems with this writing:
1. Concurrency will definitely bring about disorder, so you should consider the business scenarios that need to be sorted.
It is thread-insecure, and concurrent read and write will panic.

Optimize it:

func GetManyBase(userIds []int64) []UserBase {
	userCaches := make([]UserBase, len(userIds))

	var scene 

	var wg 
	for index, userId := range userIds {
		(1)
		go func(index int, userId int64, userCaches []UserBase) {
			(userId, NewUserCache(userId).GetBase())
			()
		}(index, userId, userCaches)
	}
	()

	i := 0
	for _, userId := range userIds {
		if value, ok := (userId); ok {
			userCaches[i] = value.(UserBase)
		}
		i++
	}

	return userCaches
}

Why not lock it directly?

  • Because after my tests, it will be very slow and not as good as the optimization.
  • This ensures order.

This is the article about Go using coroutines to obtain data in batches and speeding up the interface return. For more related Go using coroutines to obtain data in batches, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!