Learning Go(1) Vector Add In Go

Every time I started to learn a new programming language that supports multi-threading, the “hello world” program to me is vector adding. Vector adding is very simple in logic, and involves almost every fundamental operations that a parallel program would use, such as creating threads, waiting for threads finished, and setting mutex, etc. So, implementing a vector add program in go is a good stepping stone for me to learn how to use goroutine to achieve concurrency.

Before started I did not expect achieving concurrency is that easy in golang. Only one line is needed to create a single thread, compared with how it goes in C++/Python – adding long and clumsy clauses to create threadpool and create threads, that is so easy. Thus I did not pay much effort and get the vector adding program as following.

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"sync"
)

func vec_add(a, b, c []int) {
	// assert(len(a) == len(b))
	if len(a) != len(b) {
		panic("[!] length of two array are not the same\n")
	}
	
	/* thread function of vector add */
	routine_add := func(start, end int, wg *sync.WaitGroup) {
		for i := start; i < end; i++ {
			c[i] = a[i] + b[i]
		}
		wg.Done()
		return
	}

	fmt.Println("[+] using", runtime.NumCPU(), "cpu cores")
	routine_bound := len(a) / runtime.NumCPU()
	routine_remain := len(a) % runtime.NumCPU()

	var wg sync.WaitGroup
	
	/* spawn goroutines to work */
	for i := 0; i < runtime.NumCPU(); i++ {
		var bound int
		if i == runtime.NumCPU()-1 {
			bound = routine_remain + routine_bound
		} else {
			bound = routine_bound
		}

		wg.Add(1)
		start := routine_bound * i
		end := routine_bound*i + bound
		go routine_add(start, end, &wg)
	}

	/* wait until all goroutine finished */
	wg.Wait()
	fmt.Println("[+] vec_add finished")
}

func main() {
	array_size := 25
	
	/* randomly assign values to elements in vector a, b*/
	a := rand.Perm(array_size)
	b := rand.Perm(array_size)
	for i, _ := range a {
		a[i]++
		b[i]++
	}
	/* allocate a slice to store the result */
	c := make([]int, array_size)
	
	/* call kernel function*/
	vec_add(a, b, c)

	/* print processed results */
	print_array := func(id string, arr []int) {
		fmt.Print("[-] Array ", id)
		for i,_:=range arr {
			fmt.Print(" ", arr[i])
		}
		fmt.Println()
	}
	print_array("a", a)
	print_array("b", b)
	print_array("c", c)
}

Output:

$ go build vec_add.go -o vec_add
$ vec_add 
[+] using 8 cpu cores
[+] vec_add finished
[-] Array a 22 5 3 14 11 1 20 12 8 6 24 19 10 15 7 9 2 21 18 4 17 23 25 16 13
[-] Array b 11 6 4 25 1 14 15 24 3 12 18 10 9 20 2 21 22 5 16 19 13 17 7 23 8
[-] Array c 33 11 7 39 12 15 35 36 11 18 42 29 19 35 9 30 24 26 34 23 30 40 32 39 21