I have several threads waiting on the same Condvar associated with data wrapped inside a RwLock. I'd like to call Condvar::wait and check a condition after holding the write lock to the data, but it seems Condvar::wait only accepts MutexGuard as an argument. Since there are many other parts that use this data as a read-only variable, I can't simply replace RwLock with Mutex.
How should I use Condvar together with RwLock-wrapped data?
See https://github.com/Amanieu/parking_lot/issues/165 for an example of using a CondVar with a RwLockGuard.
Related
Consider the following scenarios.
let mut map = HashMap::new();
map.insert(2,5);
thread::scope(|s|{
s.spawn(|_|{
map.insert(1,5);
});
s.spawn(|_|{
let d = map.get(&2).unwrap();
});
}).unwrap();
This code cannot be compiled because we borrow the variable map mutably in h1 and borrow again in h2. The classical solution is wrapping map by Arc<Mutex<...>>. But in the above code, we don't need to lock whole hashmap. Because, although two threads concurrently access to same hashmap, they access completely different region of it.
So I want to share map through thread without using lock, but how can I acquire it? I'm also open to use unsafe rust...
in the above code, we don't need to lock whole hashmap
Actually, we do.
Every insert into the HashMap may possibly trigger its reallocation, if the map is at that point on its capacity. Now, imagine the following sequence of events:
Second thread calls get and retrieves reference to the value (at runtime it'll be just an address).
First thread calls insert.
Map gets reallocated, the old chunk of memory is now invalid.
Second thread dereferences the previously-retrieved reference - boom, we get UB!
So, if you need to insert something in the map concurrently, you have to synchronize that somehow.
For the standard HashMap, the only way to do this is to lock the whole map, since the reallocation invalidates every element. If you used something like DashMap, which synchronizes access internally and therefore allows inserting through shared reference, this would require no locking from your side - but can be more cumbersome in other parts of API (e.g. you can't return a reference to the value inside the map - get method returns RAII wrapper, which is used for synchronization), and you can run into unexpected deadlocks.
The docs for parking_lot say:
Mutex and RwLock allow raw unlocking without a RAII guard object.
Mutex<()> and RwLock<()> allow raw locking without a RAII guard object.
There is no further mention of these features, what they mean and how to use them. What are some pointers or sample uses?
The Mutex API controls access to its data via a guard, which unlocks the Mutex when it goes out of scope. The Mutex owns its data and can enforce that it is only accessible through a MutexGuard when it is locked. Both std::sync::Mutex and parking_lot::Mutex are the same in this regard.
However, parking_lot::Mutex also exposes its internals, which are a raw pointer to the data and a RawMutex. A RawMutex is just a lock, which does not control access to the data, but just tracks the state of the lock.
One reason to use RawMutex might be for times when it is very inconvenient to keep a MutexGuard in scope, and you are prepared to manage the lock status yourself. This is more likely in a library that defines new synchronization primitives or smart pointers rather than in application code, but you might also find it useful if you were mechanically translating existing C/C++ code to Rust.
By way of a simple example, these functions do the same thing as each other, but one uses the unsafe RawMutex:
use parking_lot::{Mutex, lock_api::RawMutex as _};
fn update_mutex(mutex: &Mutex<i32>) {
let mut guard = mutex.lock();
*guard = 2;
// guard goes out of scope here, causing the Mutex to be unlocked
}
fn update_mutex_raw(mutex: &Mutex<i32>) {
let raw_mutex = unsafe { mutex.raw() };
let data = mutex.data_ptr();
raw_mutex.lock();
unsafe {
*data = 2;
// need to manually unlock the RawMutex
raw_mutex.unlock();
};
}
RawMutex.unlock() is unsafe because it would trigger Undefined Behaviour to call it when the murex is not locked. Dereferencing the data pointer twice at once would be Undefined Behaviour too, so it's up to you to make sure you don't, by honouring the lock state of the RawMutex.
As always, when using unsafe functions, read the documentation carefully and make sure you thoroughly undersand the invariants that you must preserve in order to avoid Undefined Behaviour.
Rust guarantees that safe code will not contain data races (concurrent mutable access to the same object). Mutexes allow for threads to have mutually exclusive access to read/write an object, thereby avoiding the race.
In other languages, (Java and C++ come to mind) mutexes aren't explicitly associated with data. It is up to programmers to make sure they lock and unlock them appropriately, accessing the data only within the critical section between the lock and unlock. In Rust, this would mean that safe code could contain data races if things were written incorrectly.
The solution to this is the RAII guard. The mutex "owns" the associated object and only allows read/write access through a RAII guard that represents a lock on the mutex. This is the MutexGuard type returned by std's Mutex::lock()
Parking_lot is claiming to allow locking/unlocking without creation of a RAII guard, which can be useful when writing unsafe code doing fancy things with mutexes for speed reasons. This differentiates it from std's sync::Mutex, which does not provide these methods.
The primary limitation of RAII guards is that by the nature of RAII they only last as long as the enclosing scope. Further, they hold a reference to the mutex, so "storing" the "lock" past its scope is hard due to Rust's borrowing rules.
The referenced parking_lot methods are the unsafe raw_unlock and the safe raw_lock. Since a raw_lock() needs an associated raw_unlock() to finish the critical section, use of these features means delving into unsafe code, which is usually ill-advised and unnecessary unless you have good reason to believe its the only way to accomplish what you need.
Basically, the title is self-explanatory.
I use it in following way:
The code is in Objective-C++.
Objective-C classes make concurrent calls to different purpose functions.
I use std::mutex to lock and unlock std::vector<T> editing option across entire class, as C++ std containers are not thread safe.
Using lock_guard automatically unlocks the mutex again when it goes out of scope. That makes it impossible to forget to unlock it, when returning, or when an exception is thrown. You should always prefer to use lock_guard or unique_lock instead of using mutex::lock(). See http://kayari.org/cxx/antipatterns.html#locking-mutex
lock_guard is an example of an RAII or SBRM type.
The std::lock_guard is only used for two purposes:
Automate mutex unlock during destruction (no need to call .unlock()).
Allow simultaneous lock of multiple mutexes to overcome deadlock problem.
For the last use case you will need std::adopt_lock flag:
std::lock(mutex_one, mutex_two);
std::lock_guard<std::mutex> lockPurposeOne(mutex_one, std::adopt_lock);
std::lock_guard<std::mutex> lockPurposeTwo(mutex_two, std::adopt_lock);
On the other hand, you will need allocate yet another class instance for the guard every time you need to lock the mutex, as std::lock_guard has no member functions. If you need guard with unlocking functionality take a look at std::unique_lock class. You may also consider using std::shared_lock for parallel reading of your vector.
You may notice, that std::shared_lock class is commented in header files and will be only accessible with C++17. According to header file you can use std::shared_timed_mutex, but when you will try to build the app it will fail, as Apple had updated the header files, but not the libc++ itself.
So for Objective-C app it may be more convenient to use GCD, allocate a couple of queue for all your C++ containers at the same time and put semaphores where needed. Take a look at this excellent comparison.
I have some data of type T which implements neither Copy nor Clone.
If I want to share my data between several threads immutably, I'll use Arc<T>.
If I want to share it mutably, I'll use Arc<Mutex<T>>.
What if I want to share it first mutably, and then immutably in a loop? So:
I cannot use either Arc<T> or Arc<Mutex<Arc<T>>> because I will not be able to mutate the data in the "mutable" threads.
I can use Arc<Mutex<T>>, but then I have to lock() it in each of the "immutable" threads to reach T, losing parallelism.
I cannot copy data after mutation is complete, because it is expensive (or there is no way to implement Clone).
What is the right effective solution then?
A std::sync::RwLock is what I am looking for, thanks #Shepmaster!
For some use-cases (cache for example), Arc<Mutex<Arc<T>>> is an interesting solution.
The main difference with RwLock: you can get the value and use it without keeping the read lock.
fn get<T>(mutex: &Arc<Mutex<Arc<T>>>) -> Arc<T> {
mutex.lock().unwrap().clone()
}
Full example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2a2db2cd8aebf6a246486679f841827a
Please bear with me on this as I'm new to this.
I have an array and two threads.
First thread appends new elements to the array when required
myArray ~= newArray;
Second thread removes elements from the array when required:
extractedArray = myArray[0..10];
myArray = myArray[10..myArray.length()];
Is this thread safe?
What happens when the two threads interact on the array at the exact same time?
No, it is not thread-safe. If you share data across threads, then you need to deal with making it thread-safe yourself via facilities such as synchronized statements, synchronized functions, core.atomic, and mutexes.
However, the other major thing that needs to be pointed out is that all data in D is thread-local by default. So, you can't access data across threads unless it's explicitly shared. So, you don't normally have to worry about thread safety at all. It's only when you explicitly share data that it's an issue.
this is not thread safe
this has the classic lost update race:
appending means examening the array to see if it can expand in-place, if not it needs to make a (O(n) time) copy while the copy is busy the other thread can slice of a piece and when the copy is done that piece will return
you should look into using a linked list implementation which are easier to make thread safe
Java's ConcurrentLinkedQueue uses the list described here for it's implementation and you can implement it with the core.atomic.cas() in the standard library
It is not thread-safe. The simplest way to fix this is to surround array operations with the synchronized block. More about it here: http://dlang.org/statement.html#SynchronizedStatement