37. Other languages - Go
Section author: Sophia Mulholland <smulholland505gmail.com>
37.1. Hello World in Go
Go is similar in syntax to C, yet simpler in many ways. C definitely has more structure and syntax rules that Go does not have, which makes Go simpler and easier to learn. Writing a short program in Go looks like a combination of C and Python. Syntax like this allows you to get comfortable with the language quickly and move on to working on harder things.
First, we will get comfortable with running a Go program. In an editor such as emacs, open up a new file called “hello.go”.
The first program to write in Go is a simple “Hello World” which is shown below:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Notice: Every Go program should have a ‘package main’ defined at the top of the code, with the majority of programs using that package. The line ‘import “fmt”’ is basically like writing ‘#include <stdio.h>’ in C. It imports the package ‘fmt’ that allows you to print things. The last thing to notice is that Go does not have any semicolons.
Compile and run this code with
$ go build hello.go
$ ./hello
$ Hello, World!
OR optionally the Go compiler supports a ‘go run’ command which will just compile and run it in the same step:
$ go run hello.go
$ Hello, World!
37.2. Writing a Go program with command line arguments
Please see the chapter on differential equations for the context to this program. It is meant to graph the output of using the euler method to solve equations and compare it with the exact answer.
Let’s write another simple program to get familiar with the syntax of the language called ‘euler-method.go’.
package main
import (
"fmt"
"math"
"os"
"strconv"
)
func differential (x float64, y float64) float64 {
return (2*x)-2
}
func solution (sx float64, sy float64) float64 {
return math.Pow(sx, 2) - (2*sx)
}
func main() {
var n_steps int = 1
if len(os.Args) == 1 {
n_steps = 1000
} else if len(os.Args) == 2 {
n_steps, _ = strconv.Atoi(os.Args[1])
} else {
fmt.Println("error usage is %s [n_steps]\n", os.Args[0])
os.Exit(1)
}
var set_y float64 = 0
// define initial x, initial y, step size, and # of steps
var interval float64 = 10
var dt float64 = interval/float64(n_steps)
var xi float64 = 0
var yi float64 = set_y
for j := 0; j < int(n_steps); j++ {
//find exact solution to compare approximation with
var exact float64 = solution(xi, yi)
//solve differential with (xi, yi)
var m float64 = differential(xi, yi)
//use tangent line to approximate next y value from previous y value
var new_y float64 = yi + dt * m
//increase x by step size
var new_x float64 = xi + dt
xi = new_x
yi = new_y
fmt.Println(xi, yi, exact)
}
}
As we can see in the code, Go has a different way of declaring variables and handling command line arguments. In Go, handling command line arguments is simpler as you can import the package “os” and not have to deal with the argc and *argv[] or understanding pointers yet. By just importing “os” you can deal with command line inputs as an array and can check the length of that array rather than dealing with the number argc.
To deal with converting this array of string inputs into integers, we can import “strconv” which converts the strings into ints. However, this strconv function converts it into a number plus a <nil> at the end so avoid this compiler error when we try to declare n_steps, we can declare the variable n_steps along with the blank identifier ‘_’ to avoid having to declare all the return values (<nil>).
$ go build euler-method.go
$ ./euler-method 1000 > euler-method.dat
gnuplot> plot "euler-method.dat" using 1:2 with lines
gnuplot> replot "euler-method.dat" using 1:3 with lines
37.3. Goroutines and channels
A cool thing that Go does well is run goroutines. These are functions that can run concurrently with other functions. Concurrency means that the functions are running at the same time rather than waiting for one to finish and then the next program starts. Basically it is used when we need to handle multiple things at the same time. Go makes these kinds of programs simple to write and easier to understand because concurrency is generally difficult to understand in computing. Also, these goroutines do not take up a lot of memory and so they are very fast.
Threads are most commonly used to run things concurrently. All processes have at least one main thread that executes a task. Threads take a fixed amount of memory in the stack to create and when many threads are used, switching between them gets messy.
However, using goroutines is like a step above threads. The programmer deals with them while the computer does not even know goroutines exist. They take little memory to create, 2kB, which means you can have millions of them running on one CPU. They can also shift in size, to accommodate the stack. Go also deals with the switching of goroutines for the programmer, pausing and running. This is an advantage to goroutines because the programmer does not need to say ahead of time when to stop and start the threads.
Let’s see how to implement these goroutines.
package main
import (
"fmt"
)
func hello() {
fmt.Println("Hello world")
}
func main() {
go hello()
fmt.Println("main function")
}
We have a function called hello, which looks just like a normal declaration of a function and what makes it a goroutine is putting ‘go’ in front of the function call. So let’s run it.
$ go build goroutines-hello.go
$ ./goroutines-hello
The output is
$ main function
That’s weird. We called the function hello(), and it did not print “Hello world” like we wanted. That is because by making it a goroutine, the compiler did NOT wait for it to finish its task of printing “Hello world” and it went on with the rest of the main function. When the main function finishes, the program is over no matter what, and hello() did not have enough time to finish.
Let’s write a program that deals with more goroutines and introduce a way to make sure the program waits for the goroutines to finish.
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello World")
}
func listnumbers() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
func squareroot(x int) {
if x == 2 {
fmt.Println("Oh no, a 2")
} else {
fmt.Println("good choice")
}
}
func main() {
//run all three goroutines
go hello()
go listnumbers()
go squareroot(3)
//wait for the goroutines to finish
time.Sleep(2 * time.Second)
fmt.Println("All finished")
}
$ go build goroutines.go && ./goroutines
If we ran it in the exact same way, the goroutines would not have time to finish so let’s help it do everything it’s supposed to. Notice how we added a line for the computer to “sleep” as the goroutines finished and it we run it, it prints out everything we wanted it to. If you run it multiple times you’ll find the order of the outputs changes, as depending on which goroutine is running on which core, it will finish first.
So these goroutines are cleaner and easier to implement than in C where you need pointers and lots of lines of code where it can get messy quickly.
So, in these programs we have no way of knowing when the goroutines end. Therefore, we have to make the program wait for two seconds to be sure it does. This is where channels come in.
A way goroutines can communicate with each other is through channels. Channels can send and receive data of one type. If a goroutine sends something to a channel, it automatically waits until a goroutine can receive that data. It will be blocked until the goroutine requests that data and vice versa.
//channels-example.go
package main
import (
"fmt"
"time"
)
func pinger (c chan string) {
for i := 0; ; i++ {
//send "ping" to the channel
c <- "ping"
}
}
//channel gets "ping" and waits until printer() is ready to receive "ping"
func printer (c chan string) {
for {
//receive "ping" from channel and assign it to msg
msg := <- c
fmt.Println(msg)
time.Sleep(time.Second)
}
}
func main() {
//makes a channel called c
//strings are passed on it
var c chan string = make(chan string)
go pinger(c)
go printer(c)
//input receives data from the channel c
//input := <- c
//will print ping
//fmt.Println(input)
var input string
//scans the input from c channel and prints it out
//does not stop until you exit the program running
fmt.Scanln(&input)
}
$ go build channels-example.go && ./channels-example
$ ping
So this program created the channel c, and ran two goroutines (pinger() and printer()). Pinger() sends “ping” to the channel and Printer() prints out the message it received from the channel. In the main function, we also show that the variable ‘input’ can receive data from the channel too and print it out.
So goroutines allow functions to be run concurrently and channels are how these goroutines communicate. Go offers an easy way to implement them and it is
37.4. More complicated Go program
Lastly, let’s take a look at a more complicated program from the differential equations chapter rewritten in Go.
package main
import (
"fmt"
"math"
)
// acceleration due to gravity
var g = 9.8
// physics values for spring equation
var k = 3.0
var m = 2.0
// physics for air friction
var air_friction = 3
// physics for frictional damping force
var damp = 0.5
var F0 = 10.0 // constant driving force
func Acceleration (t float64, v float64, x float64) float64 {
return ((-k*x) - (damp*v) + (F0*math.Cos(8.0*t))) / m
}
func Harmonic_exact (t float64, st float64, sx float64, sv float64) float64 {
//return math.Cos(t)
return sx*math.Cos(math.Sqrt(k/m - damp*damp/(4*m*m))*t)*math.Exp((-damp/(2*m)) * t)
}
func Falling_body_exact (t float64, st float64, sx float64, sv float64) float64 {
return sx + sv*t - 0.5*g*math.Pow(t,2)
}
func Damped_spring () {
//set variables for falling_body_exact()
var set_t float64 = 0
var set_x float64 = 5 /* try 10 for falling body, 5 for harmonic */
var set_v float64 = 0 /* try 10 for falling body, 0 for harmonic */
//initial variables
var interval float64 = 0.001 // how many seconds
var n_steps float64 = 1000000
var dt = interval/n_steps // size of steps
var ti = set_t
var xi = set_x
var vi = set_v
for j := 0; j < int(n_steps); j++ {
var exact float64 = Harmonic_exact(ti, set_t, set_x, set_v)
var acc float64 = Acceleration(ti, vi, xi)
var new_v = vi + (dt * acc)
var new_x = xi + (dt * vi)
// increase t by dt
var new_t = ti + dt
vi = new_v
xi = new_x
ti = new_t
fmt.Println(ti, vi, xi, exact);
}
}
func main () {
Damped_spring();
}
So this program outputs a solution to a differential equation solved by Euler’s method. The length is pretty much the same as the C program however you can see there are no double types in Go. Instead, it offers the equivalent float64 type which indicates it requires 64 bits in memory.
$ go build damped-spring-with-friction-plain.go
$ ./damped-spring-with-friction-tut > damped-spring.dat
gnuplot> plot “damped-spring.dat” using 1:3 with lines
gnuplot> replot “damped-spring.dat” using 1:4 with line
This output is the same as the chapter on differential equations, go to that chapter for an explanation on what it means. That chapter has the same program as this damped spring one, except it is written in C.
SOURCES
*https://github.com/vladimirvivien/go-cshared-examples*
*https://golangbot.com/hello-world/*
*https://medium.com/rungo/achieving-concurrency-in-go-3f84cbf870ca*
*https://www.sohamkamani.com/blog/2017/08/24/golang-channels-explained/*