samedi 28 janvier 2023

In what scope and how should a std::random_device be defined?

I want to run two concurrent threads both of which call a function (e.g. void channel()) and that function needs access to a few objects namely a std::random_device, a PRNG engine, and two std::uniform_int_distribution objects. However I am not sure where I should define each of these objects. 3 different options come to my mind:

  1. Globally
  2. Inside the channel function as thread_local (this one seems more natural to me)
  3. In the body of each of thread functions (i.e. thread1 and thread2) as automatic variables.

Which one is the most efficient and least problematic? And is there any other option that might be better?

Here's an MRE (link):

#include <chrono>
#include <random>
#include <thread>
#include <fmt/core.h>
#include <fmt/std.h>

using std::chrono_literals::operator""ms;

// globally defined
// extern thread_local std::random_device rand_dev;
// extern thread_local std::random_device rand_dev { };

void
channel( /*std::mt19937& mtgen*/ )
{

    // inside the function as thread_local
    thread_local std::random_device rand_dev { };
    thread_local std::mt19937 mtgen { rand_dev( ) };
    thread_local std::uniform_int_distribution uniform_50_50_dist { 1, 2 };

    if ( uniform_50_50_dist( mtgen ) == 1 )
    {
        thread_local std::uniform_int_distribution<size_t> uniform_dist_for_bit_select { 0, 10 };
        const auto random_index { uniform_dist_for_bit_select( mtgen ) };

        std::this_thread::sleep_for( 100ms );
        fmt::print( "thread: {} produced {}\n", std::this_thread::get_id( ),
                                                random_index );
    }
}

void thread1( )
{
    // inside the actual thread as automatic storage duration
    // std::random_device rand_dev { };
    // std::mt19937 mtgen { rand_dev( ) };

    for ( size_t count { }; count < 10; ++count )
    {
        channel( /*mtgen*/ );
    }
}

void thread2( )
{
    // inside the actual thread as automatic storage duration
    // std::random_device rand_dev { };
    // std::mt19937 mtgen { rand_dev( ) };

    for ( size_t count { }; count < 5; ++count )
    {
        channel( /*mtgen*/ );
    }
}

int main( )
{
    std::jthread th1 { thread1 };
    std::jthread th2 { thread2 };
}

Now I may have to mention that I want each thread to have a separate std::random_device and a separate std::mt19937 engine so that I don't have to share them between threads (because that way I'll have to synchronize them using e.g. mutexes). Although I guess using a single std::random_device is possible by locking its mutex with a std::scoped_lock before accessing it via its call operator().




Aucun commentaire:

Enregistrer un commentaire