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 }
Test
To test your implementation, type the following in the terminal.
cargo test --release --test count