Parallel Implementation
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
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.
Using Async Rust (OPTIONAL, recommended)
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 usetokio::runtime::Handle::current()
to get the handle of tokio async runtime, then create an async task inside your blocking distributor.rustpub 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 usetokio::spawn()
to create an async task for sendingAliveCellCount
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.
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.
rustasync { // 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 looprust// In a new thread with blocking context loop { std::thread::sleep(Duration::from_secs(2)); // Do your periodic tasks here }
Run your implementation
When running your implementation, you will get the average Game of Life iteration turns per second (Avg turns/s).
You can use this data to evaluate your performance and compare it with your various implementations. However, we do not recommend directly using this data in your report. We recommend using formal benchmarking instead, and don't forget to analyse your methods and results in your report.
To run your implementation, type the following in the terminal.
To stop the program, press CTRL+C twice in the terminal.
cargo run --release -- --headless --threads 4
The
--headless
argument disables the SDL visualiser for this run, as we haven't implemented it yet.The
--threads
argument stands for the number of threads passed to structParams
for this run; you can specify this number yourself.
Test
To test your implementation, type the following in the terminal.
cargo test --release --test count