[SOLVED] Help with shared data and mutexes


#1

Hi, guys.

I’m trying to port a small golang library to rust (just for learning the language). And now I’ve stucked with the concurrency thing. Here is a structure (in go):

type Decoder struct {
	reader *bufio.Reader
	line   string
	mu     sync.Mutex
}

func (dec *Decoder) Decode() (m *Message, err error) {

	dec.mu.Lock()
	dec.line, err = dec.reader.ReadString(delim)
	dec.mu.Unlock()
        ...
}

The lock is used inside the structure method. It’s not a problem in go to pass the structure around other threads. But how to make it in rust?

As I understand I must wrap the whole structure with Arc<Mutex<_>> and this structure doesn’t need to know about concurrency at all. Or is there another solution how to deal with it? I have something like that in mind:

struct Example {
    lock: Mutex<()>,
    buffer: ...
}

impl Example {
    pub fn new() -> Example {
         Example {
              lock: Mutex::new(()),
              ...
         }
    }

    pub fn decode(&self) -> ... {
         let mut buf = String::new();
         self.lock.lock().unwrap();
         self.buffer.read(&mut buf);
         ...
    }
}

fn main() {
    let ex = Arc::new(Example::new());

    {
        let ex = ex.clone();
        thread::spawn(move || {
            ex.decode();
        })
    }
}

but I guess it’s not going to compile.


#2

There are two somewhat orthogonal issues here.

First of all, there is a common wisdom that you should protect data and not the code with a mutex. Rust allows (and even requires!) you to follow this wisdom. In Rust, mutex is a container that holds data. So, the structure definitions should read as follows:

struct Example {
    buffer: Mutex<Buffer>
}

impl Example {
    pub fn new() -> Example {
        let buffer = Buffer::new();
        Example { buffer: Mutex::new(buffer) }
    }

    pub fn decode(&self) -> ... { 
        let mut buffer = self.buffer.lock().unwrap();
        // `buffer` now holds a reference to the `Buffer`. 
    }
}

The cool thing here is that Rust requires you to lock the mutex before you get access to the buffer. Moreover, there is no way you could retain the reference to the buffer after the mutex has been unlocked (with usual unsafe caveat).


#3

The second questions is about memory management. If you have two threads, and they (and only they) both have access to the same Example struct, then you need to decide, who is responsible for freeing this struct.

Generally, the last thread that finishes should free the memory. If you don’t know in which order the threads are run, than the solution is to use Arc. At run time the thread which happens to drop the count to zero will do deallocation.

But if there is some more structure in you program, like this:

fn main() {
    let foo = Example::new() // create a structure in the main thread

    // spawn 10 threads which work with `foo`

    // wait for all the children
    // we are done, can safely free the result now, because we are the only thread
}

then you can live without Arc at all, and share simple &Example. But you’ll need crossbeam crate for such scoped concurrency.

Here is the relevant piece of crossbeam docs.


#4

Thanks for great explanation! I was a bit confused by mutexes in go and by the examples where Arc and Mutex almost always go together (Arc<Mutex<_>>). Now it’s clear how to use them separately.

So that means if I have two changeable fields I have to use two mutexes for every field in the structure (and lock both of them when I need). Or it’s better to wrap the whole structure with mutex then?


#5

Either way is valid. Which is better depends on your use case. A single lock for the whole structure may reduce the number of locking/unlocking operations. It will also prevent two threads from working with different parts of the structure at the same time, which could be good (if you need to update the whole structure atomically) or bad (if you want the program to go faster by doing separate operations at the same time).