Lightning Talk: Thread Safety With synchronized_value in C++ - Jørgen Fogh - CppCon 2023
Vložit
- čas přidán 22. 04. 2024
- cppcon.org/
---
Lightning Talk: Thread Safety With synchronized_value in C++ - Jørgen Fogh - CppCon 2023
github.com/CppCon/CppCon2023
Adding thread safety to existing code is hard. The proposed type synchronized_value makes it less hard.
I will show you why.
---
Jørgen Fogh
Jørgen Fogh has been writing software his entire life. The last 15 years he has primarily done it in C++.
---
Videos Filmed & Edited by Bash Films: www.BashFilms.com
CZcams Channel Managed by Digital Medium Ltd: events.digital-medium.co.uk
---
Registration for CppCon: cppcon.org/registration/
#cppcon #cppprogramming #cpp - Věda a technologie
Made a similar class, thought I was doing something wrong when I made it since I've never seen anyone else do it. Glad there's work on what looks like a much better implementation that might make it into the standard!
A lot of people seem to have discovered this independently, which is a very good sign.
If your class is available in a repo somewhere, I would love to link to it in the "related work" section of my library's readme. I am trying to collect the best ideas so I can combine them in an improved library.
this is effectively the concept of Rust's std::sync::Mutex and I really like the pattern. I actually implemented it myself not too long ago and it makes things much clearer and safer
Why update_guard ? We moved from lock_guard to xxx_lock for mutexes and now we come back to that for synchronized_value ? Why not using the same classes than for mutexes ?
Nice! Looks very handy!
No template deduction for update_guard?
There's boost::synchronized_value as well
1:57 seems odd to me.
There is lock on map keys but value reference is still unsynchronized. Few threads can access same value by this key.
You're absolutely right. That was a bug. Well spotted!
good idea, I am eager to hear this implementation on cpp26.
Note that, while definitely *useful* as shown in this presentation, this solution is more situational, not magical:
1) As someone else commented out, this would only protect one data; if the class had multiple ones, you would have to ensure all the locks are taken before starting to modify anything so that another thread cannot see some invalid intermediate state. Which you can do of course using the std::update_guard as shown, but at that point you may also consider just having a std::mutex and manually managing it.
2) It, at best, only protects individual functions; once the function ends all locks are released, so in some situations where some thread needs to get data and handle it before modifying the class, this would still have races.
Thank you for this presentation however; it’s nice to get quick updates on what might be coming, and their use-cases!
Thanks for your comment. It's nice to know that people are paying attention.
I definitely agree that the solution isn't magical- it's just a lot better than a raw mutex.
1) The solution is to wrap all the data in a struct. I often do this directly where the fields used to be declared, which means there is only a 3 line diff to review (2 lines for the start/end of the struct and 1 line for the synchronized_value).
In most situations it wouldn't even be correct to use separate instances (and thus separate muteces) to guard related data.
2) synchronized_value is no different from a raw mutex with lock_guard or unique_lock in this regard.
Just put the update_guard in the same scope you would normally put a lock_guard.
There is basically no difference between synchronized_value and raw muteces, except you can't forget to acquire the lock.
Like all type checking, it doesn't seem to do anything once the code is correct. It's when your code _isn't_ correct that you see the benefit.
In my experience, the worse the code base is, the more helpful synchronized_value becomes.
That makes it hard to give a good example, since the example would have to be long and difficult to understand.
Personally, I expected the whole `legacy_class 3` to be wrapped into synchronised_value. Then it would make perfect sense. Wrapping internal structures looks weird to me, since it is unsafe and update_guard just downgrade the whole solution to a simple mutex.
Are there any downsides of wrapping a complex class with it?
And I wonder how non-cost operator[] would be wrapped, as well as function calls?
UPD: I checked the code, it looks fun. But I'm having really hard time understanding the limits of this approach :)
Compared to std::mutex, there are no downsides. Just be aware that synchronized_value solves some of the issues with raw mutexes, but not all of them.
As @danielmilyutin9914 pointed out above, there is actually a bug in one of my slides, because I let a reference escape the lock scope.
This can happen with or without synchronized_value, but at least you never forget to lock the mutex in the first place.
One of the places where synchronized_value really makes a difference is in legacy code with very long methods, which needs to be fixed quickly. I used to put a lock at the top of each method to make it easy to see that I hadn't forgotten any locks. That came with a performance penalty, since the scopes were longer than necessary. With synchronized_value I can just "forget" deliberately and see what breaks. Then the compiler will tell me what the narrowest (probably) safe scope is. I still need to pay attention, but takes a lot less time.
Isn’t it the same as CopperSpice’s CSLibguarded?
Sounds like a hammer for people that have no idea what they are doing when writing threaded code, (including myself most of the time.)
It is very similar and arguably better than this version.
I just implemented the current proposal for the standard, but I am working on combining the best ideas from different implementations.
I have linked to a few different versions from the github repo.