Using IP2Location Go Package in multi-threading codes

Intro

Starting with the IP2Location Go Package version 8.3.0, is now possible to perform concurrent geolocation queries with the IP2Location BIN files. In this simple example, we will show how to call the IP2Location Go Package with multiple threads.

Example code

Below we have the full example code where we launch 20 threads. Each thread will then query IP geolocation using the IP2Location Go Package for 1000 random IPs. This will simulate real life scenarios where the IPs are random.

package main

import (
  "fmt"
  "strings"
  "strconv"
  "time"
  "github.com/ip2location/ip2location-go"
  crypto_rand "crypto/rand"
  "encoding/binary"
  math_rand "math/rand"
  "sync"
)

func main() {
  maxthreads := 20
  respond := make(chan string, maxthreads)
  var wg sync.WaitGroup
  wg.Add(maxthreads)
  
  for i := 1; i <= maxthreads; i++ {
    go doQuery(respond, &wg)
  }
  
  wg.Wait()
  close(respond)
  
  for queryResp := range respond {
    fmt.Printf("Got Response:\t %s\n", queryResp)
  }
}

func doQuery(respond chan<- string, wg *sync.WaitGroup) {
  defer wg.Done()
  
  db, err := ip2location.OpenDB("./IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY.BIN")
  if err != nil {
    return
  }
  
  var ips []string
  var totaltime int64 = 0
  finalresults := ""
  maxtests := 1000
  ip := ""
  for x := 0; x < maxtests; x++ {
    ip = getIp()
    ips = append(ips, ip)
  }
  
  for index, _ := range ips {
    start := time.Now()
    results, err := db.Get_all(ips[index])
    
    if err != nil {
      fmt.Print(err)
      return
    }
    
    end := time.Now()
    duration := end.Sub(start)
    totaltime = totaltime + duration.Nanoseconds()
    
    finalresults += fmt.Sprintf("IP: %s\n", ips[index])
    finalresults += fmt.Sprintf("country_short: %s\n", results.Country_short)
    finalresults += fmt.Sprintf("country_long: %s\n", results.Country_long)
    finalresults += fmt.Sprintf("region: %s\n", results.Region)
    finalresults += fmt.Sprintf("city: %s\n", results.City)
    finalresults += fmt.Sprintf("isp: %s\n", results.Isp)
    finalresults += fmt.Sprintf("latitude: %f\n", results.Latitude)
    finalresults += fmt.Sprintf("longitude: %f\n", results.Longitude)
    finalresults += fmt.Sprintf("domain: %s\n", results.Domain)
    finalresults += fmt.Sprintf("zipcode: %s\n", results.Zipcode)
    finalresults += fmt.Sprintf("timezone: %s\n", results.Timezone)
    finalresults += fmt.Sprintf("netspeed: %s\n", results.Netspeed)
    finalresults += fmt.Sprintf("iddcode: %s\n", results.Iddcode)
    finalresults += fmt.Sprintf("areacode: %s\n", results.Areacode)
    finalresults += fmt.Sprintf("weatherstationcode: %s\n", results.Weatherstationcode)
    finalresults += fmt.Sprintf("weatherstationname: %s\n", results.Weatherstationname)
    finalresults += fmt.Sprintf("mcc: %s\n", results.Mcc)
    finalresults += fmt.Sprintf("mnc: %s\n", results.Mnc)
    finalresults += fmt.Sprintf("mobilebrand: %s\n", results.Mobilebrand)
    finalresults += fmt.Sprintf("elevation: %f\n", results.Elevation)
    finalresults += fmt.Sprintf("usagetype: %s\n", results.Usagetype)
    finalresults += fmt.Sprintf("addresstype: %s\n", results.Addresstype)
    finalresults += fmt.Sprintf("category: %s\n", results.Category)
    finalresults += fmt.Sprintf("api version: %s\n", ip2location.Api_version())
    finalresults += fmt.Sprintln("=========================================================")
  }
  
  db.Close()
  
  totaltime64 := float64(totaltime / 1000000)
  maxtests64 := float64(maxtests)
  finalresults += fmt.Sprintf("Total time: %vms\n", totaltime64)
  finalresults += fmt.Sprintf("Average time: %vms\n", (totaltime64 / maxtests64))
  
  respond <- finalresults
}

func getIp() string {
  var b [8]byte
  _, err := crypto_rand.Read(b[:])
  
  if err != nil {
    panic("Cannot seed math/rand package with cryptographically secure random number generator")
  }
  math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
  
  var s []string
  p := math_rand.Perm(255)
  for _, r := range p[:4] {
    // fmt.Println(r)
    s = append(s, strconv.Itoa(r))
  }
  ip := strings.Join(s, ".")
  
  return ip
}

The main function

In our main function, we are creating a buffered channel so we can get values from our 20 threads. We are also creating a WaitGroup so we can collect results from all 20 threads before we output them.

To spawn a new thread, we call our doQuery function with the go keyword. Both the channel and the WaitGroup are passed as parameters to the doQuery function so that they can return their results and notify the WaitGroup when the function is done processing.

The doQuery function

In this function, we creating an instance of the DB object by calling the ip2location.OpenDB function. This DB object called db functions independently from the db object in the other threads. The db object is the one performing the geolocation queries.

After instantiating the db object, we generate a list of random IPs for our tests using the getIp function. Then we feed the test IPs to the db.Get_all method which will return all the geolocation information. We will append the results into a string. Each loop will check the geolocation data for the current IP in the loop then keep appending the results. When the loop is done, we will call db.Close to close the db object.

Once all tests have concluded, the result string is returned to the calling code via the channel. Our code also calculates the average time taken per query in that particular thread.

Conclusion

As you can see, it is quite straightforward to perform multi-threaded geolocation queries using the IP2Location Go Package. You can adapt the codes to check your web visitor’s IP address and so much more.

Was this article helpful?

Related Articles