{ "cells": [ { "cell_type": "markdown", "id": "4e3ae322", "metadata": {}, "source": [ "# Multithreading in Rust" ] }, { "cell_type": "markdown", "id": "947ff014", "metadata": {}, "source": [ "## Managing Threads\n", "\n", "### Creating and joining threads\n", "\n", "* Creating a thread using `std::thread::spawn`\n", "* Joining threads to wait for their completion using `join()`" ] }, { "cell_type": "code", "execution_count": 6, "id": "e8d84fec", "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Counting 1 in background ThreadId(2)\n", "Counting 1 in spawned ThreadId(3)\n", "Counting 2 in background ThreadId(2)\n", "Counting 2 in spawned ThreadId(3)\n", "Counting 3 in background ThreadId(2)\n", "Counting 3 in spawned ThreadId(3)\n", "Counting 4 in background ThreadId(2)\n", "Counting 4 in spawned ThreadId(3)\n", "Counting 5 in spawned ThreadId(3)\n", "Counting 5 in background ThreadId(2)\n", "Counting 6 in spawned ThreadId(3)\n", "Counting 6 in background ThreadId(2)\n", "Counting 7 in background ThreadId(2)\n", "Counting 7 in spawned ThreadId(3)\n", "Counting 8 in background ThreadId(2)\n", "Counting 8 in spawned ThreadId(3)\n", "Counting 9 in spawned ThreadId(3)\n", "Counting 9 in background ThreadId(2)\n", "Counting 10 in spawned ThreadId(3)\n", "Counting 10 in background ThreadId(2)\n" ] } ], "source": [ "use std::thread;\n", "\n", "fn background_counting() {\n", " for i in 1..=10 {\n", " println!(\"Counting {} in background {:?}\", i, thread::current().id());\n", " thread::sleep(std::time::Duration::from_millis(50));\n", " }\n", "}\n", "\n", "let thd_1 = thread::spawn(background_counting);\n", "\n", "let thd_2 = thread::spawn(|| {\n", " for i in 1..=10 {\n", " println!(\"Counting {} in spawned {:?}\", i, thread::current().id());\n", " thread::sleep(std::time::Duration::from_millis(50));\n", " }\n", "});\n", "\n", "thd_1.join().unwrap();\n", "thd_2.join().unwrap();" ] }, { "cell_type": "markdown", "id": "d64ca60d", "metadata": {}, "source": [ "## Sharing Data Between Threads\n", "\n", "\n", "### Transfer Ownership\n", "\n", "* Safely transfer ownership of data to threads\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "80c5a805", "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Processing data_vec in thread ThreadId(4)\n", "Sum of data_vec: 500500\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let data_vec = (1..=1000).collect::>();\n", "\n", "let thd_process = thread::spawn(move || {\n", " println!(\"Processing data_vec in thread {:?}\", thread::current().id());\n", " thread::sleep(std::time::Duration::from_millis(500));\n", " let sum: i32 = data_vec.iter().sum();\n", " // panic!(\"Intentional panic after processing data_vec with sum {}\", sum);\n", " sum\n", "});\n", "\n", "match thd_process.join() {\n", " Ok(sum) => println!(\"Sum of data_vec: {}\", sum),\n", " Err(e) => println!(\"Thread panicked: {:?}\", e),\n", "}" ] }, { "cell_type": "markdown", "id": "72eaed46", "metadata": {}, "source": [ "### Shared Ownership - Immutable Data\n", "\n", "* Using `Arc` for shared ownership across threads" ] }, { "cell_type": "code", "execution_count": 8, "id": "01bd107d", "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Min in thread ThreadId(6): 1\n", "Max in thread ThreadId(6): 100\n", "Sum in thread ThreadId(5): 5025\n", "Final Results - Total Sum: 5025, Min: 1, Max: 100\n" ] } ], "source": [ ":dep rand = \"0.9\"\n", "\n", "use rand::random_range;\n", "use std::iter::from_fn;\n", "use std::sync::Arc;\n", "\n", "let data: Vec = from_fn(|| Some(random_range(1..=100))).take(100).collect();\n", "let shared_data = Arc::new(data);\n", "\n", "let thd_sum = {\n", " let data_clone = Arc::clone(&shared_data);\n", " thread::spawn(move || {\n", " let sum: i32 = data_clone.iter().sum();\n", " println!(\"Sum in thread {:?}: {}\", thread::current().id(), sum);\n", " sum\n", " })\n", "}; \n", "\n", "let thd_max = {\n", " let data_clone = Arc::clone(&shared_data);\n", " thread::spawn(move || {\n", " let min = data_clone.iter().min().cloned().unwrap();\n", " println!(\"Min in thread {:?}: {}\", thread::current().id(), min);\n", " let max = data_clone.iter().max().cloned().unwrap();\n", " println!(\"Max in thread {:?}: {}\", thread::current().id(), max);\n", " (min, max)\n", " })\n", "};\n", "\n", "let total_sum = thd_sum.join().unwrap();\n", "let (min_value, max_value) = thd_max.join().unwrap();\n", "\n", "println!(\"Final Results - Total Sum: {}, Min: {}, Max: {}\", total_sum, min_value, max_value);" ] }, { "cell_type": "markdown", "id": "85f2b2a1", "metadata": {}, "source": [ "### Shared Mutable State Between Threads" ] }, { "cell_type": "code", "execution_count": 2, "id": "3c499020", "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "use std::sync::{Arc, Mutex};\n", "\n", "pub struct BankAccount {\n", " // balance: f32\n", " balance: Mutex,\n", "}\n", "\n", "impl BankAccount {\n", " pub fn new(initial_balance: f32) -> Self {\n", " BankAccount { balance: Mutex::new(initial_balance) }\n", " }\n", " \n", " pub fn deposit(&self, amount: f32) {\n", " *self.balance.lock().unwrap() += amount;\n", " }\n", " \n", " pub fn withdraw(&self, amount: f32) -> Result<(), String> {\n", " let mut balance = self.balance.lock().unwrap();\n", " if amount > *balance {\n", " Err(\"Insufficient funds\".to_string())\n", " } else {\n", " *balance -= amount;\n", " Ok(())\n", " }\n", " }\n", "\n", " fn transfer(&self, to: &BankAccount, amount: f32) -> Result<(), String> {\n", " let mut from_balance = self.balance.lock().unwrap();\n", " if amount > *from_balance {\n", " return Err(\"Insufficient funds\".to_string());\n", " }\n", " let mut to_balance = to.balance.lock().unwrap();\n", "\n", " *from_balance -= amount;\n", " *to_balance += amount;\n", " \n", " Ok(())\n", " }\n", " \n", " pub fn balance(&self) -> f32 {\n", " *self.balance.lock().unwrap()\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 3, "id": "aa2240cd", "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "fn pay_in(account: Arc, amount: f32, count: usize) {\n", " for _ in 0..count {\n", " if account.balance() < 200.0 {\n", " println!(\"Balance low: ${}, depositing ${}\", account.balance(), amount);\n", " account.deposit(amount);\n", " } \n", "\n", " std::thread::sleep(std::time::Duration::from_millis(10));\n", " }\n", "}\n", "\n", "fn take_out(account: Arc, amount: f32, count: usize) {\n", " for _ in 0..count {\n", " match account.withdraw(amount) {\n", " Ok(_) => println!(\"Withdrew ${}, new balance: ${}\", amount, account.balance()),\n", " Err(e) => println!(\"Failed to withdraw ${}: {}\", amount, e),\n", " }\n", "\n", " std::thread::sleep(std::time::Duration::from_millis(20));\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 4, "id": "a22be054", "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Withdrew $30, new balance: $70\n", "Balance low: $70, depositing $50\n", "Balance low: $120, depositing $50\n", "Withdrew $30, new balance: $140\n", "Balance low: $140, depositing $50\n", "Balance low: $190, depositing $50\n", "Withdrew $30, new balance: $210\n", "Withdrew $30, new balance: $180\n", "Balance low: $180, depositing $50\n", "Withdrew $30, new balance: $200\n", "Withdrew $30, new balance: $170\n", "Withdrew $30, new balance: $140\n", "Withdrew $30, new balance: $110\n", "Withdrew $30, new balance: $80\n", "Withdrew $30, new balance: $50\n" ] } ], "source": [ "fn main() {\n", " let mut account = Arc::new(BankAccount::new(100.0));\n", "\n", " let mut account_for_pay_in = Arc::clone(&account);\n", " let pay_in_thread = std::thread::spawn(move || pay_in(account_for_pay_in, 50.0, 10));\n", "\n", " let mut account_for_take_out = Arc::clone(&account);\n", " let take_out_thread = std::thread::spawn(move || take_out(account_for_take_out, 30.0, 10));\n", "\n", " pay_in_thread.join().unwrap();\n", " take_out_thread.join().unwrap();\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "id": "7f7968af", "metadata": {}, "source": [ "## Send and Sync" ] }, { "cell_type": "markdown", "id": "e02fb112", "metadata": {}, "source": [ "* The `Send` and `Sync` traits affect the ability to transfer and share data between threads\n", "* Types that implement `Send` can be transferred to another thread\n", " * Most primitive types in Rust implement `Send`\n", " * Owned types like `String` and `Vec` also implement `Send`\n", " * Types that do not implement `Send` include raw pointers and types that manage non-thread-safe resources: `Rc`, `RefCell`\n", "* Types that implement `Sync` can be referenced from multiple threads safely\n", " * Immutable references (`&T`) are `Sync` if `T` is `Sync`\n", " * Mutable references (`&mut T`) are not `Sync`\n", " * Types that do not implement `Sync` include `Cell` and `RefCell`\n", " * Types like `Mutex` and `RwLock` provide safe shared mutable access across threads" ] }, { "cell_type": "markdown", "id": "7c6393dc", "metadata": {}, "source": [ "## Deadlocks" ] }, { "cell_type": "code", "execution_count": 4, "id": "5ae7b3aa", "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $450, to $350\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $320, to $480\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $290, to $510\n", "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $460, to $340\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $310, to $490\n", "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $440, to $360\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $330, to $470\n", "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $420, to $380\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $350, to $450\n", "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $400, to $400\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $370, to $430\n", "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $380, to $420\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $390, to $410\n", "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $360, to $440\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $410, to $390\n", "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $340, to $460\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $430, to $370\n", "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $320, to $480\n", "Transferred $30 from BankAccount#2 to BankAccount#1. New balances: from $450, to $350\n", "Transferred $50 from BankAccount#1 to BankAccount#2. New balances: from $300, to $500\n", "Final Balance of Account A: $300\n", "Final Balance of Account B: $500\n" ] } ], "source": [ "use std::sync::{Arc, Mutex};\n", "\n", "mod Bank {\n", " use std::sync::{Arc, Mutex};\n", "\n", " pub struct BankAccount {\n", " id: u32,\n", " balance: Mutex,\n", " }\n", "\n", " impl BankAccount {\n", " pub fn new(id: u32, initial_balance: f32) -> Self {\n", " BankAccount { id, balance: std::sync::Mutex::new(initial_balance) }\n", " }\n", "\n", " pub fn transfer(&self, to: &BankAccount, amount: f32) -> Result<(), String> {\n", "\n", " let (mut from_balance, mut to_balance) = if self.id < to.id {\n", " let from_balance = self.balance.lock().unwrap();\n", " let to_balance = to.balance.lock().unwrap();\n", " (from_balance, to_balance)\n", " } else {\n", " let to_balance = to.balance.lock().unwrap();\n", " let from_balance = self.balance.lock().unwrap();\n", " (from_balance, to_balance)\n", " };\n", "\n", " //let mut from_balance = self.balance.lock().unwrap();\n", " //let mut to_balance = to.balance.lock().unwrap();\n", "\n", " *from_balance -= amount;\n", " *to_balance += amount;\n", " \n", " Ok(())\n", " }\n", " \n", " pub fn balance(&self) -> f32 {\n", " *self.balance.lock().unwrap()\n", " }\n", "\n", " pub fn id(&self) -> u32 {\n", " self.id\n", " }\n", " }\n", "\n", " pub fn transfer_funds(from: Arc, to: Arc, amount: f32, count: usize) {\n", " for _ in 0..count {\n", " match from.transfer(&to, amount) {\n", " Ok(_) => println!(\"Transferred ${} from BankAccount#{} to BankAccount#{}. New balances: from ${}, to ${}\", \n", " amount, from.id(), to.id(), from.balance(), to.balance()),\n", " Err(e) => println!(\"Failed to transfer ${} from BankAccount#{} to BankAccount#{}: {}\", \n", " amount, from.id(), to.id(), e),\n", " }\n", "\n", " std::thread::sleep(std::time::Duration::from_millis(15));\n", " }\n", " }\n", "\n", "}\n", "\n", "fn main() {\n", " let account_a = Arc::new(Bank::BankAccount::new(1, 500.0));\n", " let account_b = Arc::new(Bank::BankAccount::new(2, 300.0));\n", "\n", " let account_a_for_transfer_1 = Arc::clone(&account_a);\n", " let account_b_for_transfer_1 = Arc::clone(&account_b);\n", "\n", " let thd_transfer_from_a_to_b = std::thread::spawn(move || {\n", " Bank::transfer_funds(account_a_for_transfer_1, account_b_for_transfer_1, 50.0, 10);\n", " });\n", "\n", " let account_a_for_transfer_2 = Arc::clone(&account_a);\n", " let account_b_for_transfe_2 = Arc::clone(&account_b);\n", " let thd_transfer_from_b_to_a = std::thread::spawn(move || {\n", " Bank::transfer_funds(account_b_for_transfe_2, account_a_for_transfer_2, 30.0, 10);\n", " });\n", "\n", " thd_transfer_from_a_to_b.join().unwrap();\n", "\n", " println!(\"Final Balance of Account A: ${}\", account_a.balance());\n", " println!(\"Final Balance of Account B: ${}\", account_b.balance());\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "id": "52220484", "metadata": {}, "source": [ "## Thread Communication With Channels" ] }, { "cell_type": "markdown", "id": "b3653ab8", "metadata": {}, "source": [ "* Using channels (`std::sync::mpsc`) for thread communication simplifies data transfer between threads\n", "* Channels provide a way to send data from one thread to another safely\n", " * Create a channel with `mpsc::channel()` - returns a sender and a receiver\n", " * Send data between threads using the `send()` method on the sender\n", " * Receive data in the main thread using the `recv()` method or iterating over the receiver" ] }, { "cell_type": "code", "execution_count": 5, "id": "de2665bc", "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Received: Hello from the sender thread\n" ] } ], "source": [ "use std::thread;\n", "use std::sync::mpsc;\n", "\n", "fn main() {\n", " let (sender, receiver) = mpsc::channel();\n", "\n", " let sender_clone = sender.clone();\n", " let thd_sender = thread::spawn(move || {\n", " let message = \"Hello from the sender thread\".to_string();\n", " sender_clone.send(message).unwrap();\n", " });\n", "\n", " let received_data = receiver.recv().unwrap();\n", " println!(\"Received: {}\", received_data);\n", "\n", " thd_sender.join().unwrap();\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "id": "74bd9f3b", "metadata": {}, "source": [ "### Message Passing with Structs" ] }, { "cell_type": "code", "execution_count": 6, "id": "73c689b8", "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Received Message - ID: 42, Content: Important data\n" ] } ], "source": [ "use std::thread;\n", "use std::sync::mpsc;\n", "\n", "struct CustomMessage {\n", " id: u32,\n", " content: String,\n", "}\n", "\n", "fn main() {\n", " let (sender, receiver) = mpsc::channel();\n", "\n", " let sender_clone = sender.clone();\n", " let handle = thread::spawn(move || {\n", " let message = CustomMessage {\n", " id: 42,\n", " content: \"Important data\".to_string(),\n", " };\n", " sender_clone.send(message).unwrap();\n", " });\n", "\n", " let received_message = receiver.recv().unwrap();\n", " println!(\"Received Message - ID: {}, Content: {}\", received_message.id, received_message.content);\n", "\n", " handle.join().unwrap();\n", "}\n", "\n", "main();" ] } ], "metadata": { "kernelspec": { "display_name": "Rust", "language": "rust", "name": "rust" }, "language_info": { "codemirror_mode": "rust", "file_extension": ".rs", "mimetype": "text/rust", "name": "Rust", "pygment_lexer": "rust", "version": "" } }, "nbformat": 4, "nbformat_minor": 5 }