Fomalhaut
Fomalhaut

Reputation: 9745

How do I write to a file from different threads in Rust?

I am trying to find a way to lock writing to a file in different threads. What is the correct way to do it? I would like not to open and close the file all the time when I need to write something, because of the performance.

The code I tried is:

use std::{fs, mem, thread};
use std::os::unix::prelude::FileExt;
use std::sync::{Arc, Mutex};


const COUNT: usize = 1000;


fn main() {
    let file: fs::File = fs::File::create("foo.txt").unwrap();

    let v: [u32; COUNT] = [6; COUNT];

    let counter = Arc::new(Mutex::new(0));

    let mut threads = Vec::new();
    for _ in 0..COUNT {
        let counter = Arc::clone(&counter);

        let thread = thread::spawn(move || {
            let mut i = counter.lock().unwrap();

            let offset = (mem::size_of::<u32>() * (*i)) as u64;
            let bytes = unsafe { mem::transmute::<u32, [u8; 4]>(v[*i]) };
            file.write_all_at(&bytes, offset).unwrap();

            *i += 1;
        });
        threads.push(thread);
    }

    for thread in threads {
        thread.join().unwrap();
    }
}

And it gives the error:

move occurs because `file` has type `std::fs::File`, which does not implement the `Copy` trait

As far as I understand, the file object cannot be easily shared between the threads. I guess, I do something wrong in the whole approach regarding this issue. So what is the correct way to write into a file from different threads in Rust?

Upvotes: 6

Views: 4586

Answers (1)

Ibraheem Ahmed
Ibraheem Ahmed

Reputation: 13528

You can wrap the file in an Arc and a Mutex, which will allow it to be shared between threads, and mutated by one thread at a time:

fn main() {
    let file = Arc::new(Mutex::new(File::create("foo.txt").unwrap()));

    let v: [u32; COUNT] = [6; COUNT];

    let counter = Arc::new(Mutex::new(0));

    let mut threads = Vec::new();
    for _ in 0..COUNT {
        let counter = Arc::clone(&counter);
        let file = Arc::clone(&file);

        let thread = thread::spawn(move || {
            let mut i = counter.lock().unwrap();
            let file = file.lock().unwrap();

            let offset = (mem::size_of::<u32>() * (*i)) as u64;
            let bytes = unsafe { mem::transmute::<u32, [u8; 4]>(v[*i]) };
            file.write_all_at(&bytes, offset).unwrap();

            *i += 1;
        });
        threads.push(thread);
    }

    for thread in threads {
        thread.join().unwrap();
    }
}

Note that the read and write methods in std::os::unix::fs::FileExt take an immutable reference to self. This means that the code above would compile without the Mutex, because concurrency is managed by the os kernel. However, the result is non-deterministic given that multiple threads might try to write to the file at the same time, which is probably not what you want.

Upvotes: 9

Related Questions