Skip to Content
Course content

47: Practice Exercise: Building a Concurrent Word Counter

Click on the "Edit" button in the top corner of the screen to edit your slide content.

How do I share the source text without copying it for every thread?

This is where a lot of people trip up. If you try to pass a reference &str into a thread, the compiler will scream at you because it can't prove the text will live long enough. You could clone the string for every thread, but if you're processing a 100MB log file, that's a massive waste of RAM.

The move here is Arc (Atomic Reference Counted). Wrap your text in an Arc, and then clone the handle, not the data. Every thread gets its own pointer to the same heap allocation. I usually pair this with indices to tell each thread which "slice" of the text to look at.

use std::sync::Arc;
use std::thread;

let text = Arc::new("The quick brown fox jumps over the lazy dog".to_string());
let mut handles = vec![];

for i in 0..4 {
    let text_share = Arc::clone(&text);
    let handle = thread::spawn(move || {
        // Each thread accesses the same underlying string
        println!("Thread {} sees: {}", i, text_share);
    });
    handles.push(handle);
}

Should I use a Mutex for the map or just send results back via channels?

You can absolutely wrap a HashMap in a Mutex and have every thread lock it to increment a count. But honestly? Don't do that for this exercise. Mutex contention is a performance killer. If four threads are constantly fighting for the lock just to increment a single word count, you've basically turned your multi-threaded program back into a single-threaded one, just with more overhead.

The cleaner, "Rust-y" way is to let each thread maintain its own local HashMap. When the thread finishes its chunk of text, it sends the entire local map back to the main thread via a channel (std::sync::mpsc). Then, the main thread merges those maps into one final result. It's way faster because the threads never have to wait on each other.

How do I handle the "remainder" if the text doesn't divide evenly?

If you have 1,003 words and 4 threads, you can't just give everyone 250 words. You'll miss three. I've seen people try to over-complicate this with complex math, but the simplest approach is to calculate the chunk size and let the final thread just take "everything that's left."

If you're working with indices, just make sure your last range ends at text.len() rather than start + chunk_size. Just a heads-up: be careful with UTF-8 boundaries. If you slice a string in the middle of a multi-byte character, Rust will panic. To avoid this, I recommend splitting the text into a Vec&str of words first, then chunking that vector. It's a bit more memory, but it saves you from a debugging nightmare with byte offsets.




📋 Practical Task

Exercise: Building a Multi-Threaded Word Frequency Analyzer

Your goal is to build a program that counts the frequency of words in a large string concurrently. Instead of a single-threaded loop, you will implement a "map-reduce" style approach.

  • The Input: A long string of text (at least 20-30 words).
  • The Logic:
    1. Split the text into a Vec&str of words.
    2. Divide that vector into 4 chunks.
    3. Spawn 4 threads. Each thread should calculate the word frequencies for its chunk and store them in a local HashMap<String, usize>.
    4. Use an mpsc::channel to send these local maps back to the main thread.
    5. In the main thread, receive all 4 maps and merge them into one final HashMap.
  • The Output: Print the final word counts to the console.

Challenge: Ensure that your program handles cases where the number of words isn't perfectly divisible by 4 without crashing or skipping words.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.