Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c++ > #88225 > unrolled thread
| Started by | Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> |
|---|---|
| First post | 2022-12-22 11:41 -0800 |
| Last post | 2022-12-31 18:45 +0100 |
| Articles | 13 — 6 participants |
Back to article view | Back to comp.lang.c++
Greatly simplify multithreaded access to global object Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> - 2022-12-22 11:41 -0800
Re: Greatly simplify multithreaded access to global object Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> - 2022-12-22 11:51 -0800
Re: Greatly simplify multithreaded access to global object scott@slp53.sl.home (Scott Lurndal) - 2022-12-22 20:11 +0000
Re: Greatly simplify multithreaded access to global object Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> - 2022-12-22 12:25 -0800
Re: Greatly simplify multithreaded access to global object "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2022-12-22 15:39 -0800
Re: Greatly simplify multithreaded access to global object "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2022-12-22 15:42 -0800
Re: Greatly simplify multithreaded access to global object David Brown <david.brown@hesbynett.no> - 2022-12-22 21:41 +0100
Re: Greatly simplify multithreaded access to global object Paavo Helde <eesnimi@osa.pri.ee> - 2022-12-22 23:05 +0200
Re: Greatly simplify multithreaded access to global object David Brown <david.brown@hesbynett.no> - 2022-12-23 00:11 +0100
Re: Greatly simplify multithreaded access to global object Paavo Helde <eesnimi@osa.pri.ee> - 2022-12-22 23:01 +0200
Re: Greatly simplify multithreaded access to global object Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> - 2022-12-31 07:41 -0800
Re: Greatly simplify multithreaded access to global object Muttley@dastardlyhq.com - 2022-12-31 16:02 +0000
Re: Greatly simplify multithreaded access to global object David Brown <david.brown@hesbynett.no> - 2022-12-31 18:45 +0100
| From | Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> |
|---|---|
| Date | 2022-12-22 11:41 -0800 |
| Subject | Greatly simplify multithreaded access to global object |
| Message-ID | <b5d4afb7-1e6d-4753-bbde-963f9331720bn@googlegroups.com> |
I don't know if I've re-invented the wheel here but I can't remember having seen something like this in the C++ standard library nor Boost nor wxWidgets.
Let's say we have a multi-threaded program, it has a main GUI thread and five worker threads, giving a total of six threads.
The program at all times has a status string, which is a global 'std::string' object. All six threads read and write the global status string.
I've tried to code a solution to greatly simplify allowing all six threads to read and write the global object. Let's start off with a basic function that gives access to the global string:
string &GetStatusString(void)
{
static string str;
return str;
}
So then from another function we can call 'GetStatusString' as follows:
void Func(void)
{
// This function accesses the global status string
auto &str = GetStatusString();
str += "monkey";
}
Of course this isn't thread-safe. I want to make this thread-safe, firstly by making the following two changes to 'Func' as follows:
void Func(void)
{
// This function accesses the global status string
auto str = GetStatusString(); // Change 1: no longer auto&
str.obj += "monkey"; // Change 2: 'str.obj' instead of 'str'
}
Next I change the function 'GetStatusString' as follows:
ObjectReserver<string> GetStatusString(void)
{
static string str;
static recursive_mutex mtx;
return ObjectReserver(mtx,str);
}
The last thing to do now is write the code for 'ObjectReserver'. Here's what I've got so far:
#include <mutex> // recursive_mutex, unique_lock
template<typename T>
class ObjectReserver final {
std::unique_lock<std::recursive_mutex> m_lock;
public:
T &obj;
ObjectReserver(std::recursive_mutex &argM, T &argO)
: m_lock(argM), obj(argO) {}
ObjectReserver(void) = delete;
ObjectReserver(ObjectReserver const &) = delete;
ObjectReserver(ObjectReserver &&) = delete;
ObjectReserver &operator=(ObjectReserver const &) = delete;
ObjectReserver &operator=(ObjectReserver &&) = delete;
ObjectReserver *operator&(void) = delete;
ObjectReserver const *operator&(void) const = delete;
};
I'm considering using this 'ObjectReserver' template class in a complex multithreaded program I'm currently writing. I thought I'd share this here though first just in case there's already something similar in Boost or whatever.
[toc] | [next] | [standalone]
| From | Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> |
|---|---|
| Date | 2022-12-22 11:51 -0800 |
| Message-ID | <6ef1933d-9158-4032-8eee-89254272f52en@googlegroups.com> |
| In reply to | #88225 |
On Thursday, December 22, 2022 at 7:41:35 PM UTC, Frederick Virchanza Gotham wrote:
>
> template<typename T>
> class ObjectReserver final {
>
> std::unique_lock<std::recursive_mutex> m_lock;
>
I could have used "lock_guard" here, it's a more basic lock with all the functionality I need.
[toc] | [prev] | [next] | [standalone]
| From | scott@slp53.sl.home (Scott Lurndal) |
|---|---|
| Date | 2022-12-22 20:11 +0000 |
| Message-ID | <PR2pL.4177$%os8.1602@fx03.iad> |
| In reply to | #88225 |
Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> writes: > >I don't know if I've re-invented the wheel here but I can't remember having seen something like this in the C++ standard library nor Boost nor wxWidgets. > >Let's say we have a multi-threaded program, it has a main GUI thread and five worker threads, giving a total of six threads. > >The program at all times has a status string, which is a global 'std::string' object. All six threads read and write the global status string. > Rather than contending for single string updates, which can never scale, keep a string private to each thread where each thread updates the string as required without locking; when you need the full status append the six strings into a single string. All you need to synchronize is fetching the string from each thread and if you do it right, you'll likely not even need to synchronize access from the main thread to the strings (e.g. each thread has a pointer to the current status string and when the worker thread updates the status, it uses an atomic exchange to replace the pointer to the former status string with a pointer to the new status - when the main thread fetches the status it will get either the old or the new depending on how it races with the update). The main thread can append the individual strings (or use/display each status independently). The recursive mutex solution seems fragile and not scalable to large numbers of worker threads.
[toc] | [prev] | [next] | [standalone]
| From | Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> |
|---|---|
| Date | 2022-12-22 12:25 -0800 |
| Message-ID | <26edb7c8-c59e-4b41-88b4-ecc17650b582n@googlegroups.com> |
| In reply to | #88227 |
On Thursday, December 22, 2022 at 8:11:42 PM UTC, Scott Lurndal wrote: > Rather than contending for single string updates, which can never scale, > keep a string private to each thread where each thread updates the > string as required without locking; when you need the full status > append the six strings into a single string. All you need to synchronize > is fetching the string from each thread and if you do it right, you'll > likely not even need to synchronize access from the main thread to > the strings (e.g. each thread has a pointer to the current status string > and when the worker thread updates the status, it uses an atomic exchange to replace > the pointer to the former status string with a pointer to the new > status - when the main thread fetches the status it will get either > the old or the new depending on how it races with the update). The > main thread can append the individual strings (or use/display each > status independently). > > The recursive mutex solution seems fragile and not scalable to large > numbers of worker threads. I wanted to keep my original post simple and so that's why I used 'std::string' as an example. Really though I'm coding a man in the middle program that receives, modifies and forwards RS232 traffic. More than one thread has to access the various data structures that get created and edited in response to the receipt of RS232 traffic. My program is already coded, and it actually works properly, but there are data races all over the camp. I coded it quickly to get it working to help me debug a microcontroller, but now I want to clean it up and so I think I'll use ObjectReserver for this.
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2022-12-22 15:39 -0800 |
| Message-ID | <to2prh$1ga13$1@dont-email.me> |
| In reply to | #88227 |
On 12/22/2022 12:11 PM, Scott Lurndal wrote: > Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> writes: >> >> I don't know if I've re-invented the wheel here but I can't remember having seen something like this in the C++ standard library nor Boost nor wxWidgets. >> >> Let's say we have a multi-threaded program, it has a main GUI thread and five worker threads, giving a total of six threads. >> >> The program at all times has a status string, which is a global 'std::string' object. All six threads read and write the global status string. >> > > Rather than contending for single string updates, which can never scale, > keep a string private to each thread where each thread updates the > string as required without locking; when you need the full status > append the six strings into a single string. Yes. This is analogous to split counters. Each thread keeps its own count. If you want to know the sum, well, sum up all of the threads counters. It scales fairly decently. A lot better than a single shared counter! :^) > All you need to synchronize > is fetching the string from each thread and if you do it right, you'll > likely not even need to synchronize access from the main thread to > the strings (e.g. each thread has a pointer to the current status string > and when the worker thread updates the status, it uses an atomic exchange to replace > the pointer to the former status string with a pointer to the new > status - when the main thread fetches the status it will get either > the old or the new depending on how it races with the update). The > main thread can append the individual strings (or use/display each > status independently). > > The recursive mutex solution seems fragile and not scalable to large > numbers of worker threads.
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2022-12-22 15:42 -0800 |
| Message-ID | <to2q16$1ga13$2@dont-email.me> |
| In reply to | #88227 |
On 12/22/2022 12:11 PM, Scott Lurndal wrote: > Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> writes: >> >> I don't know if I've re-invented the wheel here but I can't remember having seen something like this in the C++ standard library nor Boost nor wxWidgets. >> >> Let's say we have a multi-threaded program, it has a main GUI thread and five worker threads, giving a total of six threads. >> >> The program at all times has a status string, which is a global 'std::string' object. All six threads read and write the global status string. >> > > Rather than contending for single string updates, which can never scale, > keep a string private to each thread where each thread updates the > string as required without locking; when you need the full status > append the six strings into a single string. All you need to synchronize > is fetching the string from each thread and if you do it right, you'll > likely not even need to synchronize access from the main thread to > the strings (e.g. each thread has a pointer to the current status string > and when the worker thread updates the status, it uses an atomic exchange to replace > the pointer to the former status string with a pointer to the new > status - when the main thread fetches the status it will get either > the old or the new depending on how it races with the update). The > main thread can append the individual strings (or use/display each > status independently). > > The recursive mutex solution seems fragile and not scalable to large > numbers of worker threads. recursive mutex makes be get a bit sick from time to time. Brings back some memories about having to debug some deeply nested locking issues. God damn it was bad.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2022-12-22 21:41 +0100 |
| Message-ID | <to2fdr$1f3ks$1@dont-email.me> |
| In reply to | #88225 |
On 22/12/2022 20:41, Frederick Virchanza Gotham wrote: > > I don't know if I've re-invented the wheel here but I can't remember > having seen something like this in the C++ standard library nor Boost > nor wxWidgets. > > Let's say we have a multi-threaded program, it has a main GUI thread > and five worker threads, giving a total of six threads. > > The program at all times has a status string, which is a global > 'std::string' object. All six threads read and write the global > status string. > I'd say that's your problem right there. Only allow /one/ thing to change a given variable. Problem solved. (Well, /almost/ solved - you might still need something to ensure changes are seen atomically by readers, depending on how your strings work and get updated.) I try to think of programming like electronics. Devices have inputs and outputs. It's okay to drive multiple inputs from one output, but if you try to drive one line from multiple outputs, things are going to go wrong (such as letting out the magic grey smoke that runs all electronics). When you have six devices that all have an error indicator output, you don't just join them together on one red LED. You make six error LEDs, one for each device. Or you use a multiplexer, such as an OR gate, to combine the signals safely. Do the same in your programming. So you have six separate status strings, and a "multiplexer" that combines them in some way. Maybe it serialises them to a log file, or joins them together for display, or prioritises them to show error messages at higher priority than "okay" messages. Maybe this is done by a single "update" function (which may need locking if called from different threads), or it is in a separate thread connected by queues to the original threads, or it is in a timer function called periodically in the gui thread. There are many options. But the worst idea is to have one thread set the status to "There's a meltdown on it's way" only to have it immediately overwritten by another thread saying "Everything's hunky-dory over here".
[toc] | [prev] | [next] | [standalone]
| From | Paavo Helde <eesnimi@osa.pri.ee> |
|---|---|
| Date | 2022-12-22 23:05 +0200 |
| Message-ID | <to2gqo$1fced$2@dont-email.me> |
| In reply to | #88229 |
22.12.2022 22:41 David Brown kirjutas: > But the worst idea is to have one thread set the status to "There's a > meltdown on it's way" only to have it immediately overwritten by another > thread saying "Everything's hunky-dory over here". If you look at his code, he is appending to the status string, not overwriting it. Of course, for a status string this is probably not the best approach either, but I gather this was just meant as an illustrative example.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2022-12-23 00:11 +0100 |
| Message-ID | <to2o70$1g53k$1@dont-email.me> |
| In reply to | #88232 |
On 22/12/2022 22:05, Paavo Helde wrote: > 22.12.2022 22:41 David Brown kirjutas: > >> But the worst idea is to have one thread set the status to "There's a >> meltdown on it's way" only to have it immediately overwritten by >> another thread saying "Everything's hunky-dory over here". > > If you look at his code, he is appending to the status string, not > overwriting it. Of course, for a status string this is probably not the > best approach either, but I gather this was just meant as an > illustrative example. > For my comments, it doesn't matter much how you change the common variable - the fact that you are changing it from many places is the core problem.
[toc] | [prev] | [next] | [standalone]
| From | Paavo Helde <eesnimi@osa.pri.ee> |
|---|---|
| Date | 2022-12-22 23:01 +0200 |
| Message-ID | <to2gjt$1fced$1@dont-email.me> |
| In reply to | #88225 |
22.12.2022 21:41 Frederick Virchanza Gotham kirjutas: > > I don't know if I've re-invented the wheel here but I can't remember having seen something like this in the C++ standard library nor Boost nor wxWidgets. > > Let's say we have a multi-threaded program, it has a main GUI thread and five worker threads, giving a total of six threads. > > The program at all times has a status string, which is a global 'std::string' object. All six threads read and write the global status string. [snipped implementation with a proxy containing a mutex lock] I have done such things in the past, but in retrospect this was not the best idea, mainly because it hides the thread-locking step and makes the code harder to follow and verify for correctness. Nowadays I would just make a dedicated member function of the StatusManager class which would just append to the string under a mutex lock. If it appears this is becoming a bottleneck, one can redesign the member function to e.g. move the appended string pieces to some kind of fast inter-thread queue. With a locked proxy like in your design it would be harder to rewrite the functionality.
[toc] | [prev] | [next] | [standalone]
| From | Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> |
|---|---|
| Date | 2022-12-31 07:41 -0800 |
| Message-ID | <bc9b1453-cbdb-4f88-b0c5-38135ce9229bn@googlegroups.com> |
| In reply to | #88225 |
On Thursday, December 22, 2022, I wrote:
> I don't know if I've re-invented the wheel here
> but I can't remember having seen something
> like this in the C++ standard library nor Boost
> nor wxWidgets.
I've written code that will work on C++11, and which is optimised for C++17 which has guaranteed return value optimisation:
#ifndef HEADER_INCLUSION_GUARD_RESERVER
#define HEADER_INCLUSION_GUARD_RESERVER
#include <mutex> // recursive_mutex, lock_guard, unique_lock
#include <utility> // move
template<typename T>
class Reserver final {
Reserver(void) = delete;
Reserver(Reserver const &) = delete;
//Reserver(Reserver &&) = delete; - see below
Reserver &operator=(Reserver const &) = delete;
Reserver &operator=(Reserver &&) = delete;
Reserver const volatile *operator&(void) const volatile = delete;
T &obj;
#ifdef __cpp_guaranteed_copy_elision
std::lock_guard<std::recursive_mutex> m_lock;
Reserver(Reserver &&arg) = delete;
#else
std::unique_lock<std::recursive_mutex> m_lock;
public:
Reserver(Reserver &&arg)
: m_lock( std::move(arg.m_lock) ),
obj(arg.obj) {}
#endif
public:
Reserver(std::recursive_mutex &argM, T &argO)
: m_lock(argM), obj(argO) {}
};
#endif // HEADER_INCLUSION_GUARD_RESERVER
Reserver<int> Func(void)
{
static std::recursive_mutex mtx;
static int i = 7;
return Reserver<int>(mtx,i);
}
int main(void)
{
auto i = Func();
}
[toc] | [prev] | [next] | [standalone]
| From | Muttley@dastardlyhq.com |
|---|---|
| Date | 2022-12-31 16:02 +0000 |
| Message-ID | <topmfe$434$1@gioia.aioe.org> |
| In reply to | #88320 |
On Sat, 31 Dec 2022 07:41:30 -0800 (PST) Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> wrote: >On Thursday, December 22, 2022, I wrote: >> I don't know if I've re-invented the wheel here >> but I can't remember having seen something >> like this in the C++ standard library nor Boost >> nor wxWidgets. > >I've written code that will work on C++11, and which is optimised for C++17 >which has guaranteed return value optimisation: Thats nice. What does "return value optimisation" mean in practice then?
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2022-12-31 18:45 +0100 |
| Message-ID | <topsf8$12qbr$1@dont-email.me> |
| In reply to | #88322 |
On 31/12/2022 17:02, Muttley@dastardlyhq.com wrote: > On Sat, 31 Dec 2022 07:41:30 -0800 (PST) > Frederick Virchanza Gotham <cauldwell.thomas@gmail.com> wrote: >> On Thursday, December 22, 2022, I wrote: >>> I don't know if I've re-invented the wheel here >>> but I can't remember having seen something >>> like this in the C++ standard library nor Boost >>> nor wxWidgets. >> >> I've written code that will work on C++11, and which is optimised for C++17 >> which has guaranteed return value optimisation: > > Thats nice. What does "return value optimisation" mean in practice then? > See if this helps: <https://en.cppreference.com/w/cpp/language/copy_elision>
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.c++
csiph-web