Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c++ > #88225 > unrolled thread

Greatly simplify multithreaded access to global object

Started byFrederick Virchanza Gotham <cauldwell.thomas@gmail.com>
First post2022-12-22 11:41 -0800
Last post2022-12-31 18:45 +0100
Articles 13 — 6 participants

Back to article view | Back to comp.lang.c++


Contents

  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

#88225 — Greatly simplify multithreaded access to global object

FromFrederick Virchanza Gotham <cauldwell.thomas@gmail.com>
Date2022-12-22 11:41 -0800
SubjectGreatly 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]


#88226

FromFrederick Virchanza Gotham <cauldwell.thomas@gmail.com>
Date2022-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]


#88227

Fromscott@slp53.sl.home (Scott Lurndal)
Date2022-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]


#88228

FromFrederick Virchanza Gotham <cauldwell.thomas@gmail.com>
Date2022-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]


#88234

From"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>
Date2022-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]


#88235

From"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>
Date2022-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]


#88229

FromDavid Brown <david.brown@hesbynett.no>
Date2022-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]


#88232

FromPaavo Helde <eesnimi@osa.pri.ee>
Date2022-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]


#88233

FromDavid Brown <david.brown@hesbynett.no>
Date2022-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]


#88231

FromPaavo Helde <eesnimi@osa.pri.ee>
Date2022-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]


#88320

FromFrederick Virchanza Gotham <cauldwell.thomas@gmail.com>
Date2022-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]


#88322

FromMuttley@dastardlyhq.com
Date2022-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]


#88324

FromDavid Brown <david.brown@hesbynett.no>
Date2022-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