PCLT - Channel-based Concurrency Module

Lab Class #1 (Clocks, Cats and Primes)

Your assignment repository contains some startup files for the first lab. assignment of the Go-based module.

You have 2 small problems to solve, which will act as a bit of a warm-up to programming in Go.

To submit your answers, simply push your files onto the repository. Some problems will require you to modify existing files and add new ones. The problems will not be graded, but we will use a similar system for the mini-project next week and the project the week after.


Setup

Naturally, you will need to have a recent Go distribution installed on your machine. You can check that the go tool is available on your shell’s path by typing go version.

Go Modules

From Go version 1.11 and above, the go tool now supports so-called modules, Go’s dependency management system and build system. Previously, the go tool relied on the environment variable GOPATH to find sources and dependencies to build.

The general setup of a go project with modules is as follows:

  1. Create a folder for your project (outside of GOPATH). For example, lets create a folder playground (mkdir playground).

  2. In the folder, run the command go mod init MyApp. This will setup a new module called MyApp by creating a go.mod file with the contents (your go version may vary):

    module MyApp
    
    go 1.15

The go build tool will update this file with any external dependencies that are used in the module. The significance of the module name is that all import paths in the module begin with the module name, even if the folder has a completely different name from the module name. A common idiom is to use a module name of the form github.com/btoninho/myproject. You can then use commands such as go build or go test on your module.

  1. There is often some confusion as to how packages work inside a module (or in go in general). While it is good practice to have package and folders name match, this need not be the case (and is not enforced by the go compiler). A package name is specified by the package declaration at the top of a go source file. The compiler expects that all files in the same folder live in the same package. The compiler will complain if you delcare more than one package in the same folder. For instance, the following two files f1.go and f2.go, if included in the same folder, will result in a build error:

    f1.go 
    ------------
    package p1
    
    func f1() {}
    
    f2.go 
    ------------
    package p2
    
    func f2() {}
  2. If the module includes an executable entrypoint, Go requires the program entrypoint to be in a specially named package main. This will generally be the package at the root of your project. All other packages will exist in subfolders of your module root. For instance, a module with a main package and another package would have the following dir. structure and contents:

    λ tree
    .
    ├── go.mod
    ├── hello_world.go
    └── my_other_package
       └── solver.go
    
    1 directory, 3 files
    
    λ cat hello_world.go
    package main
    
    import "fmt"
    
    func main() {
     fmt.Println("Hello world.")
    }
    
    λ cat my_other_package/solver.go
    package my_other_package
    
    func Solve() {
      //...
    }
  3. To use the my_other_package in the main package, we use:

    package main
    
    import (
      "MyApp/my_other_package"
      "fmt"
    )
    
    func main() {
      my_other_package.Solve()
      fmt.Println("Hello world.")
    }

    Note that the import path is of the form module_name/package_dir. To use the package’s exported contents, use package_name.function_name or package_name.type. This is important if by some reason the package name differs from the directory name. Go also supports qualified imports, where we locally rename an imported package. Look it up!

Now that we know how to use go modules, lets do some minor hacking.


Problem 1 - Clocks and Cats

The file clockserver/clockserver.go contains a simple, non-concurrent implementation of a clock server. The clock server listens for TCP connections on port 8080. Once a connection is established, the server sends the current time every second. The server is not programmed to do any sort of sophisticated error handling, simply logging any errors as they arise.

Try to build and run the clock server. To see what the server is sending, use the command nc localhost 8080 if you are using an Unix-based machine or (build and run) the netcat/netcat.go program. The supplied netcat is hardcoded to listen on port 8080.

  1. You may have noticed that the clock server can only handle a single client at a time. Make the clock server accept requests concurrently. This should be very easy.
  2. Modify the clock server to accept a port number and write a client program (place it under clockclient/clockclient.go) that receives from multiple clocks simultaneously (e.g. in different timezones), displaying the results in some reasonably formated way. For the client to know on which address to listen to, use a reasonable command-line argument syntax (e.g. clockclient [ClockName=ip:port]+).

Note: In a Unix-based system you can “fake” the timezone of the clock server by changing the environment variable TZ. For instance, TZ=Asia/Seoul ./clockserver will run a clockserver with the timezone of Seoul, South Korea.


Problem 2 - Concurrent Primes

Now that you are a bit more familiar with Go, its time to focus a on channels.

Write a pipeline of (infinitely running) goroutines that calculate and subsequently print out prime numbers, in sequence, using a prime sieve. Place your implementation under primes/sieve.go.

You will likely want to structure your program using three “kinds” of goroutines (connected using channels): an initial Producer routine that simply emits the stream of all natural numbers (starting at 2) in sequence; a chain of Sieve routines, connected by channels, that forwards the number stream (i.e., the first Sieve receives from the Producer and sends to the next Sieve, and so on), filtering out numbers that are divisible by a given (prime) number; and, an assembling routine that implements the chaining of Sieves and prints out the numbers in succession.

You may want to alter the implementation described above so that it can be tested. Write a (very inefficient) test that validates a few of the output numbers. Don’t worry, we will cover (some) testing later in lecture!


That’s it! Don’t forget to push!