Skip to content

Parallel Implementation

This guide is for the Rust version of the coursework, which is offered as an optional extension.

In this stage, you are required to write code to evolve Game of Life using multithreading on a single machine.

The following pages are some suggested steps to help you get started, you are not required to follow them.

Your implementation will be marked against the success criteria outlined here.

Step 3

Step 3

Now using a ticker, report the number of cells that are still alive every 2 seconds.

To report the count by sending the AliveCellsCount event. Also send the TurnComplete event after each complete iteration.

You might create a dedicated thread to send AliveCellsCount event periodically, or complete this step using async Rust.

If you're not familiar with Async Rust, you can start by reading the "Getting Started" chapter of the Async book or following this tutorial provided by tokio. However, be careful not to go too deep if you find it getting too difficult!

At this stage, distributor is a blocking function, designed to make it easier for you to write a simple implementation at the starting point.

You can complete this step using async Rust by either:

  • Remain distributor a blocking function and use tokio::runtime::Handle::current() to get the handle of tokio async runtime, then create an async task inside your blocking distributor.

    rust
    pub fn distributor(...) { // In a blocking function
        ...
        let handle = tokio::runtime::Handle::current();
        handle.spawn(async {
            async_task.await; // Do some asynchronous tasks
        });
    
        async_task.await; // `await` is only allowed inside `async` functions and blocks
        ...
    }
  • Or change distributor to an async function, and use tokio::spawn() to create an async task for sending AliveCellCount events periodically. Your invocation should look something like this:

    rust
    // in `src/gol/mod.rs`
    {
        ...
        tokio::task::spawn_blocking(move ||
            distributor(params, distributor_channels)).await??; 
        tokio::spawn(distributor(params, distributor_channels)).await??; 
        Ok(())
    }
    
    
    // in `src/gol/distributor.rs`
    pub fn distributor( 
    pub async fn distributor( 
        params: Params,
        mut channels: DistributorChannels
    ) -> Result<()> {
        ...

Note on flume channels in Async Rust

You should use send or recv for channels in blocking context, and send_async or recv_async in async context.

If you change your distributor to an async function, make sure to replace send and recv with send_async and recv_async. Otherwise, your program will be blocked when calling the recv on the channel.

rust
async { // In async context
    ...
    io_input.recv()?; 
    io_input.recv_async().await?; 
    ...
}

Note on Locks in Async Rust

If you choose async Rust for this step, you should replace std::sync::Mutex or std::sync::RwLock with tokio::sync::Mutex or tokio::sync::RwLock as well.

Use mutex.lock().await in async context, and use mutex.blocking_lock() in blocking context.

Creating a ticker

  • Creating a ticker for a periodic task is easy in async context.

    rust
    async { // In async context
        let mut ticker = tokio::time::interval(Duration::from_secs(2)); // Create a ticker
        loop {
            ticker.tick().await; // Tick every two seconds
            // Do your periodic tasks here
        }
    }
  • If you are using threads and not doing async, you can sleep in a loop

    rust
    // In a new thread with blocking context
    loop {
        std::thread::sleep(Duration::from_secs(2));
        // Do your periodic tasks here
    }

Test

To test your implementation, type the following in the terminal.

bash
cargo test --release --test count