Thread: A variable visible to all threads

  1. #1
    SAMARAS std10093's Avatar
    Join Date
    Jan 2011
    Location
    Nice, France
    Posts
    2,694

    A variable visible to all threads

    I want to write a program where, random numbers are going to be created and I am going to track down the greatest of them. Three threads are going to run in parallel.

    I do it with two methods. First I create a variable in main(), which then I pass by ref. to every thread. At the end, this variable holds the maximum value generated. When the variable is updated, I use a mutex (do I really have to?).

    The second method uses std::atomic and produces the same results (as far as I tested it).

    This is a minor example I do, in order to use in my project, where it is critical that all the threads can see the current best value found by all the threads.

    Here is the code:

    Code:
    #include <iostream>       // std::cout
    #include <thread>         // std::thread
    #include <mutex>          // std::mutex
    #include <atomic>
    #include <random>
    
    std::default_random_engine generator((unsigned int)time(0));
    int random(int n) {
      std::uniform_int_distribution<int> distribution(0, n);
      return distribution(generator);
    }
    
    std::mutex mtx;           // mutex for critical section
    std::atomic<int> at_best(0);
    
    void update_cur_best(int& cur_best, int a, int b) {
      // critical section (exclusive access to std::cout signaled by locking mtx):
      if(cur_best > a && cur_best > b)
        return;
      if(at_best > a && at_best > b)
            return;
      int best;
      if(a > b)
        best = a;
      else
        best = b;
      mtx.lock();
      cur_best = best;
      mtx.unlock();
    
    
      // or
    
      if(a > b)
        at_best = a;
      else
        at_best = b;
    }
    
    void run(int max, int& best) {
        for(int i = 0; i < 15; ++i) {
            update_cur_best(best, random(max), random(max));
        }
    }
    
    //g++ -std=c++0x -pthread px.cpp -o px
    int main ()
    {
      int best = 0;
      std::thread th1 (run, 100, std::ref(best));
      std::thread th2 (run, 100, std::ref(best));
      std::thread th3 (run, 100, std::ref(best));
    
      th1.join();
      th2.join();
      th3.join();
    
      std::cout << "best = " << best << std::endl;
      std::cout << "at_best = " << at_best << std::endl;
    
      return 0;
    }
    The questions:

    1) Are the two methods equivalent?

    From the ref: "Atomic types are types that encapsulate a value whose access is guaranteed to not cause data races and can be used to synchronize memory accesses among different threads."

    Are they equivalent by terms of the results they produce and efficiency?

    2) If they are, then why is atomic introduced? What method should I use? Speed is what I am interested in.

    3) Is there any faster method to achieve this functionality?

    Remember that for my actual project, the point is that best will have the current best value from all the threads, in order to make comparison easier.

    Probably they are not equivalent. I feel that I am not at the right track. How should I approach the problem then?
    Code - functions and small libraries I use


    It’s 2014 and I still use printf() for debugging.


    "Programs must be written for people to read, and only incidentally for machines to execute. " —Harold Abelson

  2. #2
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Quote Originally Posted by std10093 View Post
    I want to write a program where, random numbers are going to be created and I am going to track down the greatest of them. Three threads are going to run in parallel.

    I do it with two methods. First I create a variable in main(), which then I pass by ref. to every thread. At the end, this variable holds the maximum value generated. When the variable is updated, I use a mutex (do I really have to?).

    The second method uses std::atomic and produces the same results (as far as I tested it).

    This is a minor example I do, in order to use in my project, where it is critical that all the threads can see the current best value found by all the threads.

    Here is the code:

    Code:
    #include <iostream>       // std::cout
    #include <thread>         // std::thread
    #include <mutex>          // std::mutex
    #include <atomic>
    #include <random>
    
    std::default_random_engine generator((unsigned int)time(0));
    int random(int n) {
      std::uniform_int_distribution<int> distribution(0, n);
      return distribution(generator);
    }
    
    std::mutex mtx;           // mutex for critical section
    std::atomic<int> at_best(0);
    
    void update_cur_best(int& cur_best, int a, int b) {
      // critical section (exclusive access to std::cout signaled by locking mtx):
      if(cur_best > a && cur_best > b)
        return;
      if(at_best > a && at_best > b)
            return;
      int best;
      if(a > b)
        best = a;
      else
        best = b;
      mtx.lock();
      cur_best = best;
      mtx.unlock();
    
    
      // or
    
      if(a > b)
        at_best = a;
      else
        at_best = b;
    }
    
    void run(int max, int& best) {
        for(int i = 0; i < 15; ++i) {
            update_cur_best(best, random(max), random(max));
        }
    }
    
    //g++ -std=c++0x -pthread px.cpp -o px
    int main ()
    {
      int best = 0;
      std::thread th1 (run, 100, std::ref(best));
      std::thread th2 (run, 100, std::ref(best));
      std::thread th3 (run, 100, std::ref(best));
    
      th1.join();
      th2.join();
      th3.join();
    
      std::cout << "best = " << best << std::endl;
      std::cout << "at_best = " << at_best << std::endl;
    
      return 0;
    }
    Your code is wrong.

    You can't have multiple threads read from a variable like cur_best when another thread may be writing to it. You need to mutex both the reads and the writes, because you can't have one thread read, while another thread writes without synchronization. You can use a shared_mutex to allow multiple reads at the same time, but only one write. Or you can make the parameter a reference to an atomic variable.

    You also need to ensure that the best value isn't changed by another thread between the time you determine that it needs to be changed, and when you actually write to it. You can do this by checking that the value is still what you expect inside the critical section, before writing it in in mutex version. In the atomic version, you need to use compare_exchange.

    Quote Originally Posted by std10093 View Post
    1) Are the two methods equivalent?

    From the ref: "Atomic types are types that encapsulate a value whose access is guaranteed to not cause data races and can be used to synchronize memory accesses among different threads."

    Are they equivalent by terms of the results they produce and efficiency?

    2) If they are, then why is atomic introduced? What method should I use? Speed is what I am interested in.
    In general, it is possible to write the same code with either mutexes or atomics. If the data shared is small, like a primitive type, atomics are faster. If the shared memory is a large class, and only some of the members need to be read or written to, a mutex is better. Only trivially copyable types can be atomic. Large classes where you need to read and write the whole thing every time is rare, but in this case it doesn't matter which is used.

    Note if you're sharing several pieces of data that are logically connected, you need one mutex/lock, or one atomic that contains all that data.

    3) Is there any faster method to achieve this functionality?
    You can use a weaker memory order on atomics than the default. The performance gain of this depends a lot on the processor architecture. Knowing weakest memory order that you can use is considered advanced knowledge, and not worth bothering with on x86 systems.
    Last edited by King Mir; 09-08-2014 at 07:07 PM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  3. #3
    SAMARAS std10093's Avatar
    Join Date
    Jan 2011
    Location
    Nice, France
    Posts
    2,694
    Ok I see. I will have to read the theory behind the weaker memory order, but I got the big picture.

    However, in this problem it would be more reasonable to let the threads run (without communication) and then compare the best found by each one of them. I wonder if this opinion applies to my actual project too. Worth some testing I guess.

    Thanks!
    Code - functions and small libraries I use


    It’s 2014 and I still use printf() for debugging.


    "Programs must be written for people to read, and only incidentally for machines to execute. " —Harold Abelson

  4. #4
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    I wouldn't worry about weak memory order until well after you learn and are comfortable with using mutexes correctly.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  5. #5
    Registered User antred's Avatar
    Join Date
    Apr 2012
    Location
    Germany
    Posts
    257
    From your code sample above:

    Code:
    mtx.lock();
    cur_best = best;
    mtx.unlock();
    Don't lock / unlock mutexes explicitly. It's prone to errors ... not so much in such a simple case, but what happens if something throws an exception after you've locked the mutex but before you've locked it? You'll never even get to the line where you unlock the mutex. Use std::lock_guard to avoid this issue:

    Code:
    {
        // create lock guard (c'tor automatically locks the mutex)
        std::lock_guard< std::mutex > l( mtx );
    
        cur_best = best;
    } // <- close scope ... upon leaving scope, the destructor of the lock guard unlocks the mutex, even in the face of exceptions

  6. #6
    Registered User antred's Avatar
    Join Date
    Apr 2012
    Location
    Germany
    Posts
    257
    Quote Originally Posted by antred View Post
    what happens if something throws an exception after you've locked the mutex but before you've locked it?
    Hate to quote myself, but apparently I can no longer edit my post. Obviously I meant what happens if something throws an exception after you've locked the mutex but before you've unlocked it?

  7. #7
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    Quote Originally Posted by antred View Post
    Hate to quote myself, but apparently I can no longer edit my post. Obviously I meant what happens if something throws an exception after you've locked the mutex but before you've unlocked it?
    If locked, std::lock_guard releases the mutex when it goes out of scope. std::lock_guard - cppreference.com
    What can this strange device be?
    When I touch it, it gives forth a sound
    It's got wires that vibrate and give music
    What can this thing be that I found?

  8. #8
    Registered User antred's Avatar
    Join Date
    Apr 2012
    Location
    Germany
    Posts
    257
    Um, yeah, I'm aware of that. That's the whole point of me suggesting that he use std::lock_guard instead of doing it manually.

  9. #9
    SAMARAS std10093's Avatar
    Join Date
    Jan 2011
    Location
    Nice, France
    Posts
    2,694
    Thanks for the information!
    Code - functions and small libraries I use


    It’s 2014 and I still use printf() for debugging.


    "Programs must be written for people to read, and only incidentally for machines to execute. " —Harold Abelson

  10. #10
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    From your description, the way to do it is not to have three threads touch the same variable.
    Instead each thread should find its own "best" value, then once all threads have completed examine all three bests and pick the best of those three.
    No locking will be required!
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Function and variable names visible in disassembler/debugger
    By thetinman in forum C++ Programming
    Replies: 1
    Last Post: 06-14-2014, 06:28 AM
  2. Replies: 22
    Last Post: 12-14-2012, 11:00 AM
  3. Replies: 3
    Last Post: 06-30-2012, 07:59 AM
  4. Replies: 3
    Last Post: 06-04-2011, 10:07 AM
  5. How make static variable for across threads?
    By 6tr6tr in forum C++ Programming
    Replies: 10
    Last Post: 04-22-2008, 08:32 AM