LMU ☀️ CMSI 3801
LANGUAGES AND AUTOMATA I
HOMEWORK #6 PARTIAL ANSWERS

Code

Go

// It's a restaurant simulation with customers placing orders and cooks
// preparing meals. The waiter can hold 3 orders at a time, and customers
// will wait for 5 seconds before abandoning an order if the waiter is busy.
// Each of the 10 customers will eat 5 meals before leaving the restaurant.
// Oh, and the three cooks, Remy, Colette, and Linguini, actually will deliver
// the meals directly to the customers.

package main

import (
    "log"
    "math/rand"
    "sync"
    "sync/atomic"
    "time"
)

// A little utility that simulates performing a task for a random duration.
// For example, calling do(10, "Remy", "is cooking") will compute a random
// number of milliseconds between 5000 and 10000, log "Remy is cooking",
// and sleep the current goroutine for that much time.

func do(seconds int, action ...any) {
    log.Println(action...)
    randomMillis := 500 * seconds + rand.Intn(500 * seconds)
    time.Sleep(time.Duration(randomMillis) * time.Millisecond)
}

// An order for a meal is placed by a customer and is taken by a cook.
// When the meal is finished, the cook will send the finished meal through
// the reply channel. Each order has a unique id, safely incremented using
// an atomic counter. It turns out the atomic counter is necessary here
// because the counter is updated by multiple goroutines.

type Order struct {
    id uint64
    customer string
    reply chan *Order
    preparedBy string
}
var nextId atomic.Uint64

// The waiter is represented by a channel of orders. The waiter will
// take orders from customers and send them to the cook. The cook will
// then send the prepared meal back to the waiter. To simulate a waiter
// being busy, the waiter channel has a buffer of 3 orders.

var Waiter = make(chan *Order, 3)

// A cook spends their time fetching orders from the order channel,
// cooking the requested meal, and sending the meal back through the
// order's reply channel.

func Cook(name string) {
    log.Println(name, "starting work")
    for order := range Waiter {
        do(10, name, "cooking order", order.id, "for", order.customer)
        order.preparedBy = name
        order.reply <- order
    }
}

// A customer eats five meals and then goes home. Each time they enter the
// restaurant, they place an order with the waiter. If the waiter is too
// busy, the customer will wait for 5 seconds before abandoning the order.
// If the order does get placed, then they will wait as long as necessary
// for the meal to be cooked and delivered.

func Customer(name string, wg *sync.WaitGroup) {
    defer wg.Done()
    ch := make(chan *Order)
    for mealsEaten := 0; mealsEaten < 5; {
        order := &Order{id: nextId.Add(1), customer: name, reply: ch}
        log.Println(name, "placed order", order.id)
        select {
        case Waiter <- order:
            meal := <-ch
            do(2, name, "eating cooked order", meal.id, "prepared by", meal.preparedBy)
            mealsEaten += 1
        case <-time.After(7 * time.Second):
            do(5, name, "waiting too long, abandoning order", order.id)
        }
    }
    log.Println(name, "going home")
}

func main() {
    var customersDone sync.WaitGroup
    customers := []string{
        "Ani", "Bai", "Cat", "Dao", "Eve", "Fay", "Gus", "Hua", "Iza", "Jai",
    }
    for _, customer := range customers {
        customersDone.Add(1)
        go Customer(customer, &customersDone)
    }
    go Cook("Remy")
    go Cook("Colette")
    go Cook("Linguini")
    customersDone.Wait()
    close(Waiter)
    log.Println("Restaurant closing")
}

Julia

Answers vary.

Pyth

Here is an ungolfed version:

Fd}1TFN}1Tp%"%4d"*Nd)k

Exercises

  1. What is the difference between concurrency and parallelism?

    Answer: Concurrency is about managing independent tasks that may or may not run simultaneously, while parallelism specifically refers to truly simultaneous execution of tasks (on multicore or multicomputer systems).

  2. What is the difference between a thread and a task in Java? Give an example.

    Answer: A thread is an actual object, while a task is a more abstract “unit of work” or the code itself that runs concurrently with other tasks.

  3. What happens if you invoke a method on a thread that has terminated in Java? If you call an entry on a task that has terminated in Ada?

    Answer: Java: The method is called. Ada: A Tasking_Error exception is raised.

  4. Explain, for Ada, Java, and Go, when, exactly, a program terminates. That is, will it terminate when the main thread finishes, or will it wait for certain other threads to finish first?

    Answer: Ada: program terminates only when the main task and all its dependents finish, so Ada waits for other tasks. Java: the program terminates when the main thread and all non-daemon threads finish, so it waits for some but not all. Go: program terminates when main goroutine finishes, without waiting for any other goroutines.

  5. In Go, what is the difference between a buffered and unbuffered channel? Provide an example of when you would use each.

    Answer: An unbuffered channel has no actual capacity and is fully synchronous, meaning each side blocks for the other to be ready. A buffered channel has a capacity blocking only when the buffer is full (for sending) or empty (for receiving). An example use case of an unbuffered channel is to make a simple latch. An example use case for a buffered channel is an event queue.

  6. Explain the difference between a mutex and a read-write mutex (RWMutex) in Go. When would you choose one over the other?

    Answer: A plain mutex does not allow any concurrent access, while a read-write mutex allows multiple readers or one writer at a time (concurrent reads but exclusive writes). Choose a plain mutex for simple operations or when there are a lot of writes. Choose a read-write when there are many reads and few writes.

  7. What happens if you try to read from or write to a closed channel in Go? How can you detect if a channel is closed?

    Answer: Reading from a closed channel will read any queued values, but if there are none, will return the zero value immediately without blocking. Writing to a closed channel causes a panic. You can detect if a channel is closed by using the two-value assignment: value, ok := <-ch`, where ok is false if the channel is closed.

  8. Describe the select statement in Go and how it differs from a switch statement. What happens when multiple channels in a select are ready simultaneously?

    Answer: The select statement in Go waits on several channel operations and runs a case that is ready. A switch statement in many programming languages chooses what to do next based on arbitrary boolean expressions (rather than channel readiness). If more than one channel in a select is ready simultaneously, Go chooses one of the ready cases at random.