Live data from Hacker News

Inside Rust's std and parking_lot mutexes – who wins?

blog.cuongle.dev

51–60 of 134 posts

Re: Inside Rust's std and parking_lot mutexes – who wins?

#51
post #19

I will personally recommend that unless you are writing performance sensitive code*, don’t use mutexes at all because they are too low-level an abstraction. Use MPSC queues for example, or something like RCU. I find these abstractions much more developer friendly. *: You may be, since you are using Rust.

Channels and RCU can often be better for performance as well. I have run into scaling issues due to contention way too many times. Sometimes just because two things were in different parts of the same cache line.

Sharing as little mutable state as possible is often the best way to scale to a large number of CPU cores. RCU can help with that if you have data that is mostly read but rarely written. If your workload is balanced or even write heavy RCU is probably not a good option.

I have even had to redesign because I had too much contention on plain old atomic reference counters, almost 30 % of my runtime was reference counting of a small number of specific Arcs, and hardware performance counters pointed at cache line contention. I redesigned that code to avoid Arcs entirely which also allowed some additional optimisations, resulting in cutting approximately 40 % of my runtime in total.

So, each specific use case should be approached individually if you care about performance. And always profile and benchmark. If the code isn't performance critical, by all means do what you think is most maintainable. But measure, because you are probably wrong about what part of your code is the bottleneck unless you measure.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#52
post #5

Earlier quoted context omitted.

> Prior to that issue Rust was using something much worse, pthread_mutex_t Presumably you're referring to this description, from the Github Issue: > > On most platforms, these structures are currently wrappers around their pthread equivalent, such as pthread_mutex_t. These types are not movable, however, forcing us to wrap them in a Box, resulting in an allocation and indirection for our lock types. This also gets in…

> The effect of referring to a copy of the object when locking, unlocking, or destroying it is undefined. https://pubs.opengroup.org/onlinepubs/9699919799/functions/V... I.e., if I pthread_mutex_init(&some_addr, ...), I cannot then copy the bits from some_addr to some_other_addr and then pthread_mutex_lock(&some_other_addr). Hence not movable. > Moving a mutex is otherwise non-sensical once the mutex is visible What…

> What does "visible" mean here? In Rust, in any circumstance where a move is possible, there are no other references to that object, hence it is safe to move.

And other than during construction or initialization (of the mutex object, containing object, or related state), how common is it in Rust to pass a mutex by value? If you can pass by value then the mutex isn't (can't) protect anything. I'm struggling to think of a scenario where you'd want to do this, or at least why the inability to do so is a meaningful impediment (outside construction/initialization, that is). I understand Rust is big on pass-by-value, but when the need for a mutex enters the fray, it's because you're sharing or about to share, and thus passing by reference.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#53

Earlier quoted context omitted.

As I said in the article, we avoid Tokio mutexes entirely for the exact reason that being cancelled in the middle of a critical section is bad. In Rust, there are two sources of cancellations in the middle of a critical section: async cancellations and panics. Ergo, panicking in the middle of a critical section is also bad, and mutexes ought to detect that and mark their internal state as corrupted as a result.

> Ergo, panicking in the middle of a critical section is also bad, and mutexes ought to detect that and mark their internal state as corrupted as a result. I fundamentally disagree with this. Panicking in the middle of an operation that is supposed to be atomic is bad. If it's not supposed to be atomic then it's totally fine, just as panicking when you hold a plain old `&mut` is fine. Not every use of a `Mutex` is pr…

I've written many production Rust services and programs over the years, both sync and async, and in my experience—by far the most common use of mutexes is to temporarily violate invariants that are otherwise upheld while the mutex is unlocked (which I think is what you mean by "atomic"). In some cases invariants can be restored, but in many cases they simply cannot.

Panicking while in the middle of a non-mutex-related &mut T is theoretically bad as well, but in my experience, &mut T temporary invariant violations don't happen nearly as often as corruption of mutex-guarded data.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#54

The C++ example given in the article is not correct. In C++ a mutex can wrap the object being protected.

Although there are owning mutexes for C++ the C++ standard library does not provide such a thing. So the std::mutex used in the example is not an owning mutex and that example works and does what was described.

One reason not to provide the owning mutex in C++ is that it isn't able to deliver similar guarantees to Rust because its type system isn't strong enough. Rust won't let you accidentally keep a reference to the protected object after unlocking, C++ will for example.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#55
post #52

Earlier quoted context omitted.

> The effect of referring to a copy of the object when locking, unlocking, or destroying it is undefined. https://pubs.opengroup.org/onlinepubs/9699919799/functions/V... I.e., if I pthread_mutex_init(&some_addr, ...), I cannot then copy the bits from some_addr to some_other_addr and then pthread_mutex_lock(&some_other_addr). Hence not movable. > Moving a mutex is otherwise non-sensical once the mutex is visible What…

> What does "visible" mean here? In Rust, in any circumstance where a move is possible, there are no other references to that object, hence it is safe to move. And other than during construction or initialization (of the mutex object, containing object, or related state), how common is it in Rust to pass a mutex by value? If you can pass by value then the mutex isn't (can't) protect anything. I'm struggling to think…

Depends on the program, and it can be a very useful tool.

Rust has Mutex::get_mut(&mut self) which allows getting the inner &mut T without locking. Having a &mut Mutex implies you can get &mut T without locks. Being able to treat Mutex like any other value means you can use the whole suite of Rust's ownership tools to pass the value through your program.

Perhaps you temporarily move the Mutex into a shared data structure so it can be used on multiple threads, then take it back out later in a serial part of your program to get mutable access without locks. It's a lot easier to move Mutex around than &mut Mutex if you're going to then share it and un-share it.

Also It's impossible to construct a Mutex without moving at least once, as Rust doesn't guarantee return value optimization. All moves in Rust are treated as memcpy that 'destroy' the old value. There's no way to even assign 'let v = Mutex::new()' without a move so it's also a hard functional requirement.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#56
post #52

Earlier quoted context omitted.

> The effect of referring to a copy of the object when locking, unlocking, or destroying it is undefined. https://pubs.opengroup.org/onlinepubs/9699919799/functions/V... I.e., if I pthread_mutex_init(&some_addr, ...), I cannot then copy the bits from some_addr to some_other_addr and then pthread_mutex_lock(&some_other_addr). Hence not movable. > Moving a mutex is otherwise non-sensical once the mutex is visible What…

> What does "visible" mean here? In Rust, in any circumstance where a move is possible, there are no other references to that object, hence it is safe to move. And other than during construction or initialization (of the mutex object, containing object, or related state), how common is it in Rust to pass a mutex by value? If you can pass by value then the mutex isn't (can't) protect anything. I'm struggling to think…

You can pass the mutex by value and it does continue to protect its value.

https://play.rust-lang.org/?version=stable&mode=debug&editio...

Re: Inside Rust's std and parking_lot mutexes – who wins?

#57

Earlier quoted context omitted.

We're currently working on separating poison from mutexes, such that the default mutexes won't have poisoning (no more `.lock().unwrap()`), and if you want poisoning you can use something like `Mutex >`.

I'm very disappointed at this. The path of least resistance ought to be the right thing to do.

In the entire history of the standard library, we have never once seen a single report of anyone attempting to recover from poison.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#58

> Poisoning: Panic Safety in Mutexes This is one of the biggest design flaws in Rust's std, in my opinion. Poisoning mutexes can have its use, but it's very rare in practice. Usually it's a huge misfeature that only introduces problems. More often than not panicking in a critical section is fine [1], but on the other hand poisoning a Mutex is a very convenient avenue for a denial-of-service attack, since a poisoned M…

We're currently working on separating poison from mutexes, such that the default mutexes won't have poisoning (no more `.lock().unwrap()`), and if you want poisoning you can use something like `Mutex >`.

Excited to hear this.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#59

Author of the original WTF::ParkingLot here (what rust’s parking_lot is based on). I’m surprised that this only compared to std on one platform (Linux). The main benefit of parking lot is that it makes locks very small, which then encourages the use of fine grained locking. For example, in JavaScriptCore (ParkingLot’s first customer), we stuff a 2-bit lock into every object header - so if there is ever a need to do s…

I do the same in my toy JVM (to implement the reentrant mutex+condition variable that every Java object has), except I've got a rare deadlock somewhere because, as it turns out, writing complicated low level concurrency primitives is kinda hard :p

Re: Inside Rust's std and parking_lot mutexes – who wins?

#60

> Poisoning: Panic Safety in Mutexes This is one of the biggest design flaws in Rust's std, in my opinion. Poisoning mutexes can have its use, but it's very rare in practice. Usually it's a huge misfeature that only introduces problems. More often than not panicking in a critical section is fine [1], but on the other hand poisoning a Mutex is a very convenient avenue for a denial-of-service attack, since a poisoned M…

Questions for anyone who is an expert on poisoning in Rust:

Is it safe to ignore poisoned mutexes if and only if the relevant pieces of code are unwind-safe, similar to exception safety in C++? As in, if a panic happens, the relevant pieces of code handles the unwinding safely, thus data is not corrupted, and thus ignoring the poison is fine?

Post reply on HN