Company
Date Published
Author
Joseph Udonsak
Word count
3282
Language
English
Hacker News points
None

Summary

As computers further embed themselves into our way of living, increasing their performance is crucial — regardless of whether you're building a safety critical system or a simple tool to make life easier for your customers. In terms of hardware, modern computers have multi-core processors, making it possible to execute more instructions simultaneously, but this is of little benefit if your software executes instructions synchronously. Writing concurrent applications has been a big challenge — but Go changes all that. With built-in support for concurrent programming, Go makes it easy to write programs that efficiently utilise multiple CPU cores and handle concurrent tasks. It does this using goroutines, lightweight threads managed by the Go runtime. Goroutines enable the execution of functions concurrently, allowing different parts of your program to run independently. To take advantage of concurrency to reduce the execution time of your applications, you will build a Go application which generates a usage report for your Twilio account. The application will be built using the Go programming language and its built-in support for concurrent programming. The report will be a spreadsheet with four sheets containing the following information: list of accounts, all-time usage records, message records, and call records. The application will use the Excelize package to generate the Excel spreadsheet and the Twilio's Go Helper Library to interact with the Twilio API. To get started, you need to create a new folder for the application, navigate into it, and add module support using the following commands: mkdir concurrency_demo cd concurrency_demo go mod init concurrency_demo. Next, you will require the following dependencies: Excelize, GoDotEnv, and Twilio's Go Helper Library. You can add them using the following command: go get github.com/xuri/excelize/v2 github.com/joho/godotenv github.com/twilio/twilio-go. After that, create a local version of the .env file using the following command: cp .env .env.local. Then, retrieve your Twilio Auth Token and Account SID from the Twilio Console Dashboard and insert them in place of the respective placeholders in .env.local. The application will be built using two helper modules: pkg and main. The pkg module will contain code to help with retrieving Twilio records, as well as writing the results to a spreadsheet. The main module will contain the application's entry point. It is executed when the go run main.go command is executed. It starts by calling the setup() function. This retrieves the Twilio credentials from the previously created .env.local file and instantiates the Twilio client. Next, it calls the generateReportSynchronously() function. This function calls the GetAccountDetails(), GetUsageRecords(), GetMessageRecords(), and GetCallRecords() functions you declared earlier and passes the responses to the WriteResults() function. Finally, the execution time of the generateReportSynchronously() function is printed. To compare the synchronous and concurrent executions, update the main function to match the following: func main() { setup() fmt.Println("Starting synchronous execution") start := time.Now() generateReportSynchronously() fmt.Printf("Synchronous Execution Time: %s\n", time.Since(start).String()) fmt.Println("Starting concurrent execution") start = time.Now() generateReportConcurrently() fmt.Printf("Concurrent Execution Time: %s\n", time.Since(start).String()) }. To run the application to see how long both processes take, use the following command: go run main.go. You should see the result printed out as shown below: Starting synchronous execution Synchronous Execution Time: 11.94378467s Starting concurrent execution Concurrent Execution Time: 8.986716638s. While the results may vary due to your internet connection speed and processing power etc., one thing that stands out is that there is a significant difference in time between the synchronous and concurrent executions. Benchmarking the results will provide more reliable performance measurements. To benchmark the results, you need to call the setup() function which is used to set up the Twilio client. You can create new files named setup_test.go and main_test.go in the application's top-level folder and add the following code to them: package main import ( "os" "testing" ) func TestMain(m *testing.M) { setup() os.Exit(m.Run()) }. Next, you need to update the main_test.go file to match the following: package main import ("concurrency_demo/pkg" "fmt" "log" "os" "sync" "time" "github.com/joho/godotenv" "github.com/twilio/twilio-go" twilioApi "github.com/twilio/twilio-go/rest/api/v2010" ) func Benchmark_generateReportSynchronously(b *testing.B) { for i := 0; i < b.N; i++ { generateReportSynchronously() } }. func Benchmark_generateReportConcurrently(b *testing.B) { for i := 0; i < b.N; i++ { generateReportConcurrently() } }. To run the tests, use the following command: go test -bench=. You should see something similar to the following: Benchmark_generateReportSynchronously-4 1 6413687120 ns/op Benchmark_generateReportConcurrently-4 1 3667963691 ns/op. The -4 suffix for each function execution denotes the number of CPUs used to run the benchmark, as specified by GOMAXPROCS. After the function name, the number of times the loop was executed is shown (in this case once). Finally, the average execution time for each operation (in nanoseconds) is displayed. At the moment, each test is run once, and for each test only one iteration is executed. If you want, you can run each test multiple times using the count argument. You can also modify the number of iterations for each test using the benchtime argument. For example, you can run the benchmark tests five times with each test running four iterations using the following command: go test -bench=. -count=5 -benchtime=4x. Now you have a basic understanding of concurrency in Go. Well done for coming this far! By taking advantage of Go’s provisions for concurrency, you have cut your applications running time by almost 50%. This will help you build efficient applications that take more advantage of the available CPU resources, and reduce the waiting time for users, which is always an improvement for any application. However, special consideration has to be given to avoid some pitfalls. One common pitfall is deadlocks. In this case, your goroutines were writing to different fields of the struct hence there was no risk of a deadlock. However, if you encounter a situation where goroutines are reading and writing to the same field, you will need to do some more work. Go provides channels for safe communication between goroutines. Also, the sync package provides more than the WaitGroup type you saw earlier. Based on the concept of mutual exclusion, the Mutex and RWMutex types make it possible for multiple goroutines to concurrently read and write to a shared resource. The entire codebase is available on GitHub should you get stuck at any point. I’m excited to see what else you come up with. Until next time, make peace not war .