Skip to content

Session 07

Topic 2: The Go language

  • tour of Go
  • tour of Go (continued) 👈
  • Go’s programming syntax
  • many workers with Goroutines
  • shared memory between workers
  • message passing with Go channels
  • case studies with concurrency
  • performance and scalability
  • case studies with parallel algorithms

Today

Tour of Go (continued)

  1. example: animated GIFs
  2. example: web server

Go and graphics

Graphics in Go

No heavy frameworks:

  • pure standard library: image, gif, color, math
  • no external dependencies, no build pain
  • fast enough for real-time generation
  • outputs an artifact (GIF)

Everything is standard Go:

  • image creation (image, image/color)
  • animation encoding (image/gif)
  • math (math, rand)
  • I/O abstraction (io.Writer)
  • networking (net/http)

From math to animation

Visualization pipeline:

math → data → visualization → artifact

  1. parametric equations (’60 sci-fi Lissajous curves)
  2. sampling of equations at a given resolution
  3. frame-by-frame animation
  4. image encoding as GIF

gopl.io, p13:

The next program demonstrates basic usage of Go’s standard image packages, which we’ll use to create a sequence of bit-mapped images and then encode the sequence as a GIF animation. The images, called Lissajous figures, were a staple visual effect in sci-fi films of the 1960s. They are the parametric curves produced by harmonic oscillation in two dimensions, such as two sine waves fed into the \(x\) and \(y\) inputs of an oscilloscope.

Computing Lissajous curves

core logic:

for range nframes {
  rect := image.Rect(0, 0, 2*size+1, 2*size+1)
  img := image.NewPaletted(rect, palette)
  for t := 0.0; t < cycles*2*math.Pi; t += res {
    x := math.Sin(t)
    y := math.Sin(t*freq + phase)
    img.SetColorIndex(size+int(x*size+0.5),
                      size+int(y*size+0.5),
                      blackIndex)
  }
  phase += 0.1
  // ...

gopl.io, p15:

The lissajous function has two nested loops. The outer loop runs for 64 iterations, each producing a single frame of the animation. It creates a new 201&201 image with a palette of two colors, white and black. All pixels are initially set to the palette’s zero value (the zeroth color in the palette), which we set to white. Each pass through the inner loop generates a new image by setting some pixels to black. The result is appended, using the built-in append function, to a list of frames in anim, along with a specified delay of 80ms. Finally the sequence of frames and delays is encoded into GIF format and written to the output stream out.

Reusable interfaces - strength of Go

func lissajous(out io.Writer)

io.Writer is an interface used by:

  • file output (disk)
  • standard output (terminal)
  • HTTP response (web)

… and many more:
anything that implements a Write method

Two modes: CLI tool ↔ web service

func lissajous(out io.Writer)
lissajous(os.Stdout)   // terminal / command-line
lissajous(w)           // web server (w has type http.ResponseWriter)
  • single lissajous function: one core logic
  • supports both kinds of outputs: CLI or web
  • … more kind of outputs are easy to implement

🎯 No refactoring needed!

Hands-on: code/go/gif

  1. open the terminal, ssh into Matrix

  2. cd into your clone of our course repo
    code.vt.edu/jrudi/cmda3634

  3. go into the folder of the example: cd code/go/gif

  4. run the Go program, save output to .gif file with redirection “>”

    go run lissajous.go > lofi.gif
    

Copy from Matrix to your laptop

How do you see the image stored on Matrix?

  1. on Matrix, verify the folder path you’re currently in

    ls  # check if the GIF gets listed
    pwd # prints the current path
    
  2. use scp (copies over SSH) in a new terminal window:

    scp <VT_PID>@matrix.math.vt.edu:<OUTPUT_FROM_PWD> lj.gif
    
    <VT_PID>          # is your username (without @vt.edu)
    <OUTPUT_FROM_PWD> # is the output from step 1
    :                 # (a colon) separates URL and path
    lj.gif            # is the target filename (on your laptop)
    

Go and web services

A web server in Go

this line in Go starts a real HTTP server:

http.ListenAndServe("localhost:8000", nil)
  • production-grade
  • same code on laptop, cluster, or cloud
  • each incoming request is handled concurrently
    → concurrency is automatic!

The Go standard library’s package net/http does the heavy lifting:

  • routing
  • request parsing
  • response writing
  • concurrency

Request response

server responds with your custom handler function

func handler(w http.ResponseWriter, r *http.Request)
  • r → everything the client sent
  • w → everything you send back

🎯 Explicit inputs, explicit outputs!

Transfer data over the web

receive data from client using the struct r

clientURL := r.URL.Path

send data to client: just write to w like to a file

fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)

same as for CLI programs and file output:

fmt.Fprintf(io.Stdout, "URL.Path = %q\n", r.URL.Path) // CLI output
file, err := os.Open(filename)  // ... check err
fmt.Fprintf(file, "URL.Path = %q\n", r.URL.Path)      // file output

Easy to grow: This tiny server can evolve into a …

  • REST API
  • JSON service
  • metrics endpoint
  • microservice

Same core logic, many roles

One Go program can be:
CLI tool / web server / backend service
→ No context switching between ecosystems.

Why Go shines for servers:

  • fast startup, low memory
  • strong standard library
  • single static binary
  • simple concurrency model (we’ll explore that later)

Hands-on: code/go/server/v1

  1. open the terminal, ssh into Matrix
  2. cd into your clone of our course repo
    code.vt.edu/jrudi/cmda3634
  3. into the folder: cd code/go/server/v1
# first terminal window (on Matrix)
go run server.go
# ... <Ctrl+c> to stop the server (hint: cancels most terminal commands)
# second terminal window (also on Matrix)
curl http://localhost:8000/hello

the main function connects the handler function to URLs beginning with /

func main() {
  http.HandleFunc("/", handler) // each request calls handler
  log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

the handler function echos the Path component of the request URL in r

func handler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}

gopl.io, p19:

This example shows a minimal server that returns the path component of the URL used to access the server. That is, if the request is for http://localhost:8000/hello, the response will be URL.Path = "/hello".

The program is only a handful of lines long because library functions do most of the work. The main function connects a handler function to incoming URLs that begin with /, which is all URLs, and starts a server listening for incoming requests on port 8000. A request is represented as a struct of type http.Request, which contains a number of related fields, one of which is the URL of the incoming request. When a request arrives, it is given to the handler function, which extracts the path component (/hello) from the request URL and sends it back as the response, using fmt.Fprintf.

Tip: Open site of a server on Matrix

use port-forwarding with SSH

man ssh  # to read the manual
-L [bind_address:]port:host:hostport
      Specifies that connections to the given TCP port or
      Unix socket on the local (client) host are to be
      forwarded to the given host and port, or Unix socket,
      on the remote side.

-N    Do not execute a remote command. This is useful for
      just forwarding ports.
ssh -N -L 8000:localhost:8000 <VT_PID>@matrix.math.vt.edu

Source: Answer to How to open port via SSH tunnel?

Exercise

Use the server feature that returns an animated GIF (Lissajous) as presented earlier.

To get the gif image from the web server:

  • run the server program in one terminal window
go run lissajous.go web
  • download from the server program in another terminal window
curl http://localhost:8000 --output lj.gif
  1. Modify the Lissajous server to read parameter values from the URL. For example, a URL like

    http://localhost:8000/?cycles=20
    

    sets the number of cycles to 20 (the default is 5).

    Use the strconv.Atoi function to convert the string parameter into an integer (e.g., "20" → 20). You can see its documentation with the terminal command:

    go doc strconv.Atoi
    

more exercises are in the README of code/go/server

Key takeaways

  • Go’s syntax prefers clarity
  • from math to pixels in ~60 lines
  • from terminal output to web service in ~10 lines

Looking ahead

  • Go syntax: readability over cleverness
  • basic syntax
  • deeper dive into syntax

Prepare for the next session 🌱

sessions/08-todo