w3resource

Rust Program: Implement Producer-Consumer Pattern with Threads & Channels

Rust Threads and Synchronization: Exercise-2 with Solution

Write a Rust program that implements a producer-consumer pattern using threads and channels.

Sample Solution:

Rust Code:

use std::sync::{Arc, Mutex};
use std::thread;

// Define a shared queue structure with thread-safe operations
struct SharedQueue {
    data: Arc>>, // Shared data protected by a mutex
}

impl SharedQueue {
    // Create a new instance of SharedQueue
    fn new() -> Self {
        SharedQueue {
            data: Arc::new(Mutex::new(Vec::new())), // Initialize an empty vector
        }
    }

    // Method to push a value into the shared queue
    fn push(&self, value: i32) {
        // Lock the mutex to access the shared data
        let mut data = self.data.lock().unwrap();
        // Push the value into the vector
        data.push(value);
    }

    // Method to pop a value from the shared queue
    fn pop(&self) -> Option {
        // Lock the mutex to access the shared data
        let mut data = self.data.lock().unwrap();
        // Pop a value from the vector if it's not empty
        data.pop()
    }
}

fn main() {
    // Create a shared queue instance
    let shared_queue = Arc::new(SharedQueue::new());

    // Clone the Arc for the producer thread
    let producer_queue = Arc::clone(&shared_queue);
    // Clone the Arc for the consumer thread
    let consumer_queue = Arc::clone(&shared_queue);

    // Spawn a producer thread
    let producer_handle = thread::spawn(move || {
        // Produce some data and push it into the shared queue
        for i in 0..5 {
            // Call the push method of SharedQueue to push the value
            producer_queue.push(i);
            println!("Producer sent: {}", i);
            // Introduce a short delay for demonstration purposes
            thread::sleep(std::time::Duration::from_millis(100));
        }
    });

    // Spawn a consumer thread
    let consumer_handle = thread::spawn(move || {
        // Consume data from the shared queue
        loop {
            // Call the pop method of SharedQueue to pop a value
            if let Some(value) = consumer_queue.pop() {
                println!("Consumer received: {}", value);
            } else {
                // If the queue is empty, break out of the loop
                break;
            }
            // Introduce a short delay for demonstration purposes
            thread::sleep(std::time::Duration::from_millis(200));
        }
    });

    // Wait for both threads to finish
    producer_handle.join().unwrap();
    consumer_handle.join().unwrap();
} 

Output:

Standard Output
Producer sent: 0
Consumer received: 0
Producer sent: 1
Consumer received: 1
Producer sent: 2
Producer sent: 3
Consumer received: 3
Producer sent: 4
Consumer received: 4
Consumer received: 2

Explanation:

In the exercise above,

  • Import the necessary modules from the standard library: "Arc" and "Mutex" for thread-safe reference counting and mutual exclusion, and "thread" for managing threads.
  • The "SharedQueue" struct is defined to represent a shared queue with thread-safe operations. It contains a single field 'data', which is an 'Arc' wrapped around a 'Mutex' containing a vector of 'i32'.
  • Inside the "impl" block for "SharedQueue", we define methods "new()", "push()", and "pop()".
    • new creates a new instance of "SharedQueue" with an empty vector.
    • push inserts a value into the shared vector after locking the mutex to ensure exclusive access.
    • pop removes and returns a value from the shared vector if it's not empty.
  • In the main function:
    • We create an instance of "SharedQueue" wrapped in an "Arc" to share among threads.
    • Clone the "Arc" for both the producer and consumer threads to share ownership.
    • Spawn two threads using thread::spawn. One is the producer thread, and the other is the consumer thread.
    • The producer thread produces data (values from 0 to 4) and pushes them into the shared queue.
    • The consumer thread continuously consumes data from the shared queue by popping values until the queue is empty.
    • Both threads introduce short delays for demonstration purposes using thread::sleep.
    • Finally, we wait for both threads to finish execution using "join()".

Rust Code Editor:

Previous: Rust Program: Create two Threads & Print "Hello, World!".
Next: Rust Program: Calculate Factorial with Threads.

What is the difficulty level of this exercise?

Test your Programming skills with w3resource's quiz.



Become a Patron!

Follow us on Facebook and Twitter for latest update.

It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.

https://www.w3resource.com/rust/threads_and_synchronization/rust-threads-and-synchronization-exercise-2.php