2

I'm trying to translate a C++ program that uses a Bank object, with methods that change an account's balance and uses mutexes so this can happen in parallel. In this program, each account had a lock I needed to access before I can modify the data. In C++, the Bank object is global, with many locks to protect the data. In Rust, I created a Struct Bank, where I attempted to use Arc<Mutex<i32>> and Mutex<i32>, to have an issue with moving the data to the thread, because it doesn't implement Copy. I'm fully aware I can just put Arc<Mutex<Bank>>, but in order to make it more similar to the C++ code, I want to try to just protect specific data. Here's some code:
Bank.rs

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

#[derive(Debug)]
pub struct Account {
    account_id: i32,
    balance: Arc<Mutex<i64>>,
}

#[derive(Debug)]
pub struct Bank {
    num: Arc<Mutex<i32>>,
    num_succ: Arc<Mutex<i32>>,
    num_fail: Arc<Mutex<i32>>,
    accounts: Vec<Account>,
}

pub fn init(n: i32) -> Bank {
    let mut accounts: Vec<Account> = Vec::new();
    for i in 0..n {
        accounts.push(Account {
            account_id: i,
            balance: Arc::new(Mutex::new(0)),
        });
    }
    Bank {
        num: Arc::new(Mutex::new(0)),
        num_succ: Arc::new(Mutex::new(0)),
        num_fail: Arc::new(Mutex::new(0)),
        accounts: accounts,
    }
}

impl Bank {
    pub fn print_account(&self) {
        for i in &self.accounts {
            let balance_lock = i.balance.lock().unwrap();
            println!("ID# {} | {}", i.account_id, balance_lock);
        }

        println!(
            "Success: {} Fails: {}", 
            self.num_succ.lock().unwrap(), 
            self.num_fail.lock().unwrap(),
        );
    }
    pub fn record_succ(&self, message: String) {
        let mut succ_lock = self.num_succ.lock().unwrap();
        println!("{message}");
        *succ_lock+=1;
    }
    pub fn record_fail(&self, message: &str) {
        let mut fail_lock = self.num_fail.lock().unwrap();
        println!("{message}");
        *fail_lock+=1;
    }
    pub fn deposit(&self, worker_id: i32, ledger_id: i32, account_id: i32, amount: i32) {
        let account = self.accounts.get(account_id as usize).unwrap();
        let mut balance_lock = account.balance.lock().unwrap();
        *balance_lock += amount as i64;
        self.record_succ(format!("Worker {worker_id} completed ledger {ledger_id}: deposit {} into account {account_id}", *balance_lock));
    }
}

main.rs

use std::{
    process,
    thread,
    env,
};
use crate::bank::*;

pub mod bank;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 3 {
        eprintln!("Usage: {} <num_of_threads>\n", args[0]);
        process::exit(1);
    }
    let num = args[1].parse::<i32>().unwrap_or(-1);
    
    let bank = bank::init(10);
    let mut workers = Vec::new();

    for id in 0..num {
        // ERROR on move: move occurs because `bank` has type `bank::Bank`, which does not implement the `Copy` trait
        let worker = thread::spawn(move || worker(id, &bank));
        workers.push(worker);
    }
    for worker in workers {
        worker.join().unwrap();
    }
}

fn worker(worker_id: i32, bank: &Bank) {
    let mut size = i32::MAX;
    bank.deposit(worker_id, 0, 1, 800);
}

The goal is to lock specific account balances and modify in parallel. I have several methods for the Bank Struct that will do these operations on self.

5
  • It's not clear how worker(id, l, &bank) would fail due to &bank not being Copybank is behind a reference, and references are Copy, so it shouldn't matter. Can you post the full error message? Commented Nov 27, 2023 at 15:08
  • 1
    Sometimes with Rust your life is a lot easier with a message-passing method between threads than it is to try and lock things across many threads. Commented Nov 27, 2023 at 15:10
  • 1
    Please create a Minimal Reproducible Example. As irrelevant notes, using atomics is almost always better than Mutex<primitive> and using different mutexes for different fields may be a great mistake, if you need to update some fields together this could be a source of synchronization problems. Commented Nov 27, 2023 at 16:58
  • @ChayimFriedman I edited the post to make a Minimal Reproducible Example. The 'move' is the issue, because it says Bank doesn't implement Copy, but when I derive Copy, it says the Arc<Mutex>> doesn't implement Copy (in the struct) Commented Nov 27, 2023 at 20:03
  • I may have solved my issue. By using std::sync::OnceLock, I was able to make BANK global static variable, and initialize it in main(), while being able to use it in the threads without any current issues. Commented Nov 27, 2023 at 20:27

1 Answer 1

1

Instead of using Arc<Mutex> for each field, use just Mutex, and wrap the whole Bank in Arc. Then you can clone it:

use crate::bank::*;
use std::sync::Arc;
use std::{env, process, thread};

pub mod bank {
    use std::sync::{Arc, Mutex};

    #[derive(Debug)]
    pub struct Account {
        account_id: i32,
        balance: Mutex<i64>,
    }

    #[derive(Debug)]
    pub struct Bank {
        num: Mutex<i32>,
        num_succ: Mutex<i32>,
        num_fail: Mutex<i32>,
        accounts: Vec<Account>,
    }

    pub fn init(n: i32) -> Arc<Bank> {
        let mut accounts: Vec<Account> = Vec::new();
        for i in 0..n {
            accounts.push(Account {
                account_id: i,
                balance: Mutex::new(0),
            });
        }
        Arc::new(Bank {
            num: Mutex::new(0),
            num_succ: Mutex::new(0),
            num_fail: Mutex::new(0),
            accounts: accounts,
        })
    }

    impl Bank {
        pub fn print_account(&self) {
            for i in &self.accounts {
                let balance_lock = i.balance.lock().unwrap();
                println!("ID# {} | {}", i.account_id, balance_lock);
            }

            println!(
                "Success: {} Fails: {}",
                self.num_succ.lock().unwrap(),
                self.num_fail.lock().unwrap(),
            );
        }
        pub fn record_succ(&self, message: String) {
            let mut succ_lock = self.num_succ.lock().unwrap();
            println!("{message}");
            *succ_lock += 1;
        }
        pub fn record_fail(&self, message: &str) {
            let mut fail_lock = self.num_fail.lock().unwrap();
            println!("{message}");
            *fail_lock += 1;
        }
        pub fn deposit(&self, worker_id: i32, ledger_id: i32, account_id: i32, amount: i32) {
            let account = self.accounts.get(account_id as usize).unwrap();
            let mut balance_lock = account.balance.lock().unwrap();
            *balance_lock += amount as i64;
            self.record_succ(format!("Worker {worker_id} completed ledger {ledger_id}: deposit {} into account {account_id}", *balance_lock));
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 3 {
        eprintln!("Usage: {} <num_of_threads>\n", args[0]);
        process::exit(1);
    }
    let num = args[1].parse::<i32>().unwrap_or(-1);

    let bank = bank::init(10);
    let mut workers = Vec::new();

    for id in 0..num {
        // ERROR on move: move occurs because `bank` has type `bank::Bank`, which does not implement the `Copy` trait
        let worker = thread::spawn({
            let bank = Arc::clone(&bank);
            move || worker(id, &bank)
        });
        workers.push(worker);
    }
    for worker in workers {
        worker.join().unwrap();
    }
}

fn worker(worker_id: i32, bank: &Bank) {
    let mut size = i32::MAX;
    bank.deposit(worker_id, 0, 1, 800);
}
Sign up to request clarification or add additional context in comments.

2 Comments

Oh, that's pretty cool. Does this allow for the Bank's content to be updated in parallel? Such as having 2 threads doing a deposit, and as long as its not the same account, it runs both at the same time?
@Naginipython Yes. As long as they don't try to access the same Mutex, they're not blocked on each other.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.