-1

I am using a wrapper function in an external software to start a new thread, which updates a global variable, but yet this seems invisible to the main thread. I cant call join(), not to block the main thread and crash the software. boost::async, boost::thread and boost::packaged_task all behave the same way.

uint32 *Dval;

bool hosttask1()
{

        while(*Dval<10)
        {
            ++*Dval;
            PlugIn::gResultOut << " within thread global value: " << *Dval << std::endl;    
            Sleep(500);
        }


return false;
}



void SU_HostThread1(uint32 *value)
{

Dval = value; 
*Dval = 2;
PlugIn::gResultOut << " before thread: " << *value <<  " before thread global: " << *Dval << std::endl;

    auto myFuture = boost::async(boost::launch::async,&hosttask1);

    //boost::thread thread21 = boost::thread(&hosttask1);
    //boost::packaged_task<bool> pt(&hosttask1);
    //boost::thread thread21 = boost::thread(boost::move(pt)); 
}

When I call the function:

number a=0 
su_hostthread1(a)
sleep(2) //seconds
result(" function returned "+a+"  \n")

OUTPUT:

before thread value: 2 before thread global value: 2
 within thread global value: 3
 within thread global value: 4
 within thread global value: 5
 within thread global value: 6
 function returned 2  
 within thread global value: 7
 within thread global value: 8
 within thread global value: 9
 within thread global value: 10

Any ideas? Thanks in advance!

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
Don I
  • 91
  • 10
  • This is a textbook data race on `Dval`, and the behavior is undefined. – GManNickG Feb 07 '20 at 20:23
  • Your example code contains errors, making it useless to discuss. Please change it so it provides a [mcve]. As a new user here, please also take the [tour] and read [ask]. – Ulrich Eckhardt Feb 07 '20 at 21:00

3 Answers3

1

If you share data between threads, you must syncronize access to that data. The two possible ways are a mutex protecting said data and atomic operations. The simple reason is that caches and read/write reordering (both by CPU and compiler) exist. This is a complex topic though and it's nothing that can be explained in an answer here, but there are a few good books out there and also a bunch of code that gets it right.

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
  • As the SU_hostthread() function argument has to be a pointer, how would I assign it into a thread safe global atomic variable, to be used inside the thread? e.g. first define std::atomic atomicvalue, as global. Then in the SU_hostthread() function re-assign it as std::atomic_exchange(&atomicvalue, value). The "atomicvalue" should point to the same address as "value", so that the external software can read it correctly. – Don I Feb 08 '20 at 11:45
0

The following code correctly reproduces what I intend to do. Mainly, the thread updates a global variable which the main thread correctly observes.

#include "stdafx.h"
#include <iostream>

#include <boost/thread.hpp>
#include <boost/chrono.hpp>

unsigned long *dataR;

bool hosttask1()
{
    bool done = false;
    std::cout << "In thread global value: " << *dataR << "\n"; //*value11 <<  *dataL << 
    unsigned long cc = 0;
    boost::mutex m;

        while (!done)
        {
            m.lock();
            *dataR = cc;
            m.unlock();
            cc++;
            std::cout <<  "In thread loop global value: "<< *dataR << "\n";
            if (cc==5) done = true;
        }


return done;
}

void SU_HostThread1(unsigned long *value)
{
    dataR = value;
    std::cout << "Before thread value: " << *value << " Before thread global value: " << *dataR << "\n"; //*value11 <<  *dataL << 
    auto myFuture = boost::async(boost::launch::async, &hosttask1);
    return;
}

int main()
{
    unsigned long value =1;
    unsigned long *value11;
    value11 = &value;

    SU_HostThread1(value11);

    boost::this_thread::sleep(boost::posix_time::seconds(1));
    std::cout << "done with end value: " << *value11 << "\n";

    return 0;
}

output:

Before thread value: 1 Before thread global value: 1
In thread global value: 1
In thread loop global value: 0
In thread loop global value: 1
In thread loop global value: 2
In thread loop global value: 3
In thread loop global value: 4
done with end value: 4

Yet when I copy this exactly to the SDK of the external software, the main thread does not update global value. Any ideas how this is so? Thanks

output in external software:

before thread value: 1 before thread global value: 1
In thread global value: 1
In thread loop global value: 0
In thread loop global value: 1
In thread loop global value: 2
In thread loop global value: 3
In thread loop global value: 4
done with end value: 1 
Don I
  • 91
  • 10
  • The answer probably is that the main() function gets properly optimised. Again it doesn't know that you are multithreading. It "knows" you assigned the value 1 to a variable `value` you did NOT declare as volatile or atomic, and it inlined SU_HostThread1, and saw that you did not modify the value pointed to. So it just didn't bother to read it again. If you looked at the assembler it probably just printed a constant 1. – Gem Taylor Feb 18 '20 at 20:02
-2

Likely this is because the compiler doesn't generally think about multithreading when optimising your code. If has seen you code checks a value repeatedly, and it knows that in single threading that value cannot change, so it just omitted the check.

If you declare the variable as volatile, then it will probably generate less efficient code that checks more often.

Of course you have to also understand that when a value is written, there are circumstances when it may not all be written in one go, so if you are unlucky enough to read it back when it is half-written, then you get back a garbage value. The fix for that is to declare it as std::atomic (which is automatically considered volatile by the optimiser), and then even more complex code will be emitted to ensure that the write and the read cannot intersect (or different processor primitives might be used for small objects such as integers)

most variables are not shared between threads, and when they are it is up to the programmer to identify those and balance optimisation against the thread synchronisation needs during design.

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
  • 1
    No, `volatile` is not the answer for multithreading synchronization. It may work in many places, but it's not the right tool and its use doesn't offer the guarantees that the right tools (atomics or mutexes) offer. – Ulrich Eckhardt Feb 07 '20 at 21:02
  • @UlrichEckhardt Indeed, but it is a step towards the correct answer, which is why i discuss it. but atomic and proper fences and mutexes are the only practical way forward. that is a much longer discussion you are welcome to post about. – Gem Taylor Feb 07 '20 at 21:12