How to properly manage multithreading read and write access in Rust? - multithreading

I'm kind of confused by Arc and Mutex in rust. My problem is the following:
The program should use n+1 threads, where only 1 thread has write access to data and the other n threads can only read data. How can I achieve this in rust?

Use RwLock. With it, readers don't block each other (but writers still block readers and readers block writers).

Related

Is Option guaranteed to be atomic?

If I have an Option<T> and I construct T, and then assign this, will the operation be atomic, as in will other threads get either None or Some(T) but definitely not part of Some(T)?
No. Option is not designed with thread synchronization in mind.
However, you cannot observe interleaved reads because Rust's memory safety prevents you from modifying a value at all while its referenced in another thread. You would need a synchronization primitive like Mutex or RwLock to do that.
There is the atomic-option crate. Or perhaps just use an AtomicPtr.

Mutexes. What even?

I am learning about computer architecture and how operating systems work. I have a few questions about how mutexes work.
Question 1
add_to_list(&list, &elem):
mutex m;
lock_mutex(m);
...
remove_from_list(&list):
mutex m;
lock_mutex(m);
...
These two functions instantiate their own mutex, which means they point to different places in memory and so one does not lock the other and effectively doesn't accomplish what we want--list to be protected.
How do we get two different functions to use the same mutex? Do we define a global variable? If so, how do you share this global variable throughout an entire program that is potentially spread throughout multiple files?
Question 2
mutex m;
modify_A():
lock_mutex(m);
A += 1;
modify_B():
lock_mutex(m);
B += 1;
These two functions modify different spaces in memory. Does that mean I need a unique mutex for each function / or piece of data? If I were to have a global mutex variable that I used for both functions, a thread calling modify_A() would block another thread trying to call modify_B()
Which brings me to my last question...
Question 3
A mutex seems like it just blocks a thread from running a piece of code until whatever thread is currently running that same code finishes. This is to create atomicity and protect the integrity of the data being used by a thread. However, the same piece of memory can be modified from many different places in a program. Which makes me think we have to use one mutex throughout an entire program, which would result in a lot of needless blocking of other threads.
Considering that pretty much every function in a given program is going to be modifying data, if we use a single mutex throughout a program, that means each function call will be blocked while that mutex is in use by another thread, even if the data it needs to access is unrelated.
Doesn't that effectively eliminate the gains from having multiple threads? If only one thread can run at a given time?
I feel like I'm totally misunderstanding how mutexes work, so please ELI5!
Thanks in advance.
Yes, you make it a global variable, or otherwise accessible to the required functions through some kind of convenience method or whatever. Global variables can be shared between translation units too, but that's language/system dependent. In C you'd just put an extern mutex m in a header that everyone shares and then define that mutex as mutex m in exactly one of your translation units.
If you don't want changes to B to block other threads from modifying A, yes, you'd use two different mutexes. If you want to lock both at the same time, you would share the mutex.
Multiple threads can run at the same time as long as no two of them are inside the critical section protected by a certain mutex at the same time. That's the whole point - everything goes on nice and parallel, but you use the mutex to serialize access to a specific resource or critical section you need protected.
You typically use a mutex to protect some particular piece of shared data. If the vast majority of your code's time is spent accessing one single piece of shared data, then you won't get much of a performance improvement from threads precisely because only one thread can safely access that piece of shared data at a time.
If you happen to fall into this situation, there are more complex techniques than mutexes. Fortunately, it's fairly rare (unless you're implementing operating systems or low-level libraries) so you can get away with using mutexes for a very large fraction of your synchronization needs.

When or why should I use a Mutex over an RwLock?

When I read the documentations of Mutex and RwLock, the difference I see is the following:
Mutex can have only one reader or writer at a time,
RwLock can have one writer or multiple readers at a time.
When you put it that way, RwLock seems always better (less limited) than Mutex, why would I use it, then?
Sometimes it is better to use a Mutex over an RwLock in Rust:
RwLock<T> needs more bounds for T to be thread-safe:
Mutex requires T: Send to be Sync,
RwLock requires T to be Send and Sync to be itself Sync.
In other words, Mutex is the only wrapper that can make a T syncable. I found a good and intuitive explanation in reddit:
Because of those bounds, RwLock requires its contents to be Sync, i.e. it's safe for two threads to have a &ptr to that type at the same time. Mutex only requires the data to be Send, because conceptually you can think of it like when you lock the Mutex it sends the data to your thread, and when you unlock it the data gets sent to another thread.
Use Mutex when your T is only Send and not Sync.
Preventing writer starvation
RwLock does not have a specified implementation because it uses the implementation of the system. Some read-write locks can be subject to writer starvation while Mutex cannot have this kind of issue.
Mutex should be used when you have possibly too many readers to let the writers have the lock.
Mutex is a simple method of locking to control access to shared resources.
At the same time, only one thread can master a mutex, and threads with locked status can access shared resources.
If another thread wants to lock a resource that has been mutexed, the thread hangs until the locked thread releases the mutex.
Read write locks are more complex than mutex locks.
Threads using mutex lack read concurrency.
When there are more read operations and fewer write operations, read-write locks can be used to improve thread read concurrency.
Let me summarize for myself:
The implementation of read-write lock is more complex than that of
mutual exclusion lock, and the performance is poor.
The read-write lock supports simultaneous reading by multiple threads. The mutex lock does not support simultaneous reading by multiple threads, so the read-write lock has high concurrency.

Producer / Consumers problem question

I am going to implement a program where one parent process reads a text file and feeds the data he's reading into a shared memory buffer that's going to be read by some children processes. All this work will be mediated by semaphores. Let's assume the parent is going to read one character at a time from the file and the shared memory buffer contains 5 slots.
At first, I thought of only having 2 semaphores:
writeSemaphore, initialized to 5, is the semaphore that tells whether the writer is allowed to write to the buffer. when it finally goes down to 0, the parent process will be blocked until one of the children unlocks it (after having read some block).
readSemaphore, initialized to 0, is the semaphore that tells whether any of the readers is allowed to read from the buffer.
But now that I think of it, this wouldn't prevent me from having 2 consumers accessing the the shared memory at the same time. I must prevent it. So I introduced a third semaphore:
allowedToRead that is either 1 or 0, that allows or blocks access to the children processes.
Here is pseudo code for both children and parent:
Child:
while (something) {
wait(readSemaphore)
wait(allowedToRead)
<<read from shared memory>>
post(allowedToRead)
post(writeSemaphore)
}
Parent:
while (something) {
wait(writeSemaphore)
<<writes to shared memory>>
post(allowedToRead)
}
Is my reasoning correct?
Thanks
Khachik is half right. He's may be all right, but his description isn't as clear as it could be.
Firstly, where you have the parent posting allowedToRead you probably mean for it to post readSemaphore.
Secondly your code allows the parent to write at the same time as a child is reading. You say you have 5 slots. If the parent writes to a different slot than the child is reading then this is ok I suppose, but how does the child determine where to read? Is it using the same variables as the parent is using to determine where to write? You probably need some extra protection there. After all I assume the different children are all reading different slots, so if you need to prevent them treading one ach other's toes you'll need to do the same for the parent too.
Thirdly, I'd have used a mutex instead of a semaphore for allowedToRead.
Fourthly, what determines which child reads which data or is it meant to be first come first served like pigs at a slop bucket?
If the shared memory has 5 independant slots, then I'd be inclined to add a "next read" and "next write" variable. Protect those two variables with a mutex in both producer and consumers, and then use the semaphores just to block/trigger reading and writing as you are already doing. If it weren't a school exercise, you could do better using a single condition variable attached to the mutex I mentioned. When it gets signalled the parent checks if he can write and the children check if they can read. When either a read or a write occurs, signal the condition variable globally to wake everybody up to check their conditions. This has the advantage that if you have independant buffer slots then you can safely and happily have multiple consumers consuming at the same time.
No.
the writer should release readSemaphore when it write one unit of information;
the writer should acquire allowedToRead lock (0,1 semaphore is a lock/mutex) before writing to shared memory to prevent race conditions.
To simplify: consider two functions read_shared_memory, write_shared_memory, which are to read and write from/to the shared memory respectively and both acquiring/releasing the same lock before reading/writing.
The producer acquires write semaphore, calls the write function, releases the read semaphore.
The consumer acquire read semaphore, calls the read function, releases the the write semaphore.
Sure this can be implemented without read/write functions, they are just to simplify using atomic access to the shared memory. A critical section can be implemented inside produce/consume loops without additional functions.
Wikipedia describes it in more scientific way :)

Multithread read and write to a ::stl::vector, vector resource hard to release

I am writing code in VS2005 using its STL.
I have one UI thread to read a vector, and a work thread to write to a vector.
I use ::boost::shared_ptr as vector element.
vector<shared_ptr<Class>> vec;
but I find, if I manipulate the vec in both thread in the same time(I can guarantee they do not visit the same area, UI Thread always read the area that has the information)
vec.clear() seem can not release the resource. problem happend in shared_ptr, it can not release its resource.
What is the problem?
Does it because when the vector reach its order capacity, it reallocates in memory, then the original part is invalidated.
As far as I know when reallocating, iterator will be invalid, why some problem also happened when I used vec[i].
//-----------------------------------------------
What kinds of lock is needed?
I mean: If the vector's element is a shared_ptr, when a thread A get the point smart_p, the other thread B will wait till A finishes the operation on smart_p right?
Or just simply add lock when thread is trying to read the point, when the read opeation is finished, thread B can continu to do something.
When you're accessing the same resource from more than one thread, locking is necessary. If you don't, you have all sorts of strange behaviour, like you're seeing.
Since you're using Boost, an easy way to use locking is to use the Boost.Thread library. The best kind of locks you can use for this scenario are reader/writer locks; they're called shared_mutex in Boost.Thread.
But yes, what you're seeing is essentially undefined behaviour, due to the lack of synchronisation between the threads. Hope this helps!
Edit to answer OP's second question: You should use a reader lock when reading the smart pointer out of the vector, and a writer lock when writing or adding an item to the vector (so, the mutex is for the vector only). If multiple threads will be accessing the pointed-to object (i.e., what the smart pointer points to), then separate locks should be set up for them. In that case, you're better off putting a mutex object in the object class as well.
Another alternative is to eliminate the locking altogether by ensuring that the vector is accessed in only one thread. For example, by having the worker thread send a message to the main thread with the element(s) to add to the vector.
It is possible to do simultaneous access to a list or array like this. However, std::vector is not a good choice because of its resize behavior. To do it right needs a fixed-size array, or special locking or copy-update behavior on resize. It also needs independent front and back pointers again with locking or atomic update.
Another answer mentioned message queues. A shared array as I described is a common and efficient way to implement those.

Resources