简体   繁体   中英

C++\Cli Interlocked::Add for doubles - can't get Interlocked::CompareExchange alternative working

This is a follow-on to an earlier question that solved the Parallel::For syntax with thread local variables Parallel::For with local variable

The book example I am trying to implement performs a Interlocked::Add on ints, yet my implementation requires doubles. In the referenced code above I implemented a Lock routine, but that doesn't work. So I am now trying to convert a C# example into a C++\\Cli class that I can then call as a safe addition of doubles Interlocked.CompareExchange

TreadSafe.h

using namespace System;
using namespace System::Threading;

ref class ThreadSafe
{
private:
    double totalValue;

public:
    property double Total
    {
        double get()
        {
            return totalValue;
        }
    }
    double AddToTotal(double addend);

    // constructor
    ThreadSafe(void);
};

ThreadSafe.cpp

// constructor
ThreadSafe::ThreadSafe(void)
{
    totalValue = 0.0;
}

double ThreadSafe::AddToTotal(double addend)
{
    double initialValue, computedValue;
    do
    {
        initialValue = totalValue;
        computedValue = initialValue + addend;
    }
    while (initialValue != Interlocked::CompareExchange(totalValue, computedValue, initialValue));

    return computedValue;
}

This I then call in the Class that contains the Parallel:For routine, which is also listed in the referenced post above.

DataCollection.cpp

// constructor
DataCollection::DataCollection(Form1^ f1) // takes parameter of type Form1 to give acces to variables on Form1
{
    this->f1 = f1;

    ts = gcnew ThreadSafe();
}

// initialize data set for parallel processing
void DataCollection::initNumbers(int cIdx, int gIdx)
{
    DataStructure^ number;
    numbers = gcnew List<DataStructure^>();

    for (int i = 0; i < f1->myGenome->nGenes; i++)
    {
// creates collection "numbers" with data to be processed in parallel
    }
}

// parallel-for summation of scores
double DataCollection::sumScore()
{
    Parallel::For<double>(0, numbers->Count, gcnew Func<double>(this, &DataCollection::initSumScore),
                                                gcnew Func<int, ParallelLoopState^, double, double>(this, &DataCollection::computeSumScore),
                                                gcnew Action<double>(this, &DataCollection::finalizeSumScore));
    return ts->Total;
}

// returns start value
double DataCollection::initSumScore()
{
    return 0.0;
}

// perform sequence alignment calculation
double DataCollection::computeSumScore(int k, ParallelLoopState^ status, double tempVal)
{
// calls several external methods, but for testing simplified to an assignment only

    tempVal += 1.0;

    return tempVal;
}

// locked addition
void DataCollection::finalizeSumScore(double tempVal)
{
    ts->AddToTotal(tempVal);
}

The code compiles and runs, but it doesn't add. It always returns 0.

So I assume my translation / implementation of the C# example is not correct. What's wrong?

I made an extremely simple example using your ThreadSafe class.

ref class foo
{
public:
  ThreadSafe^ ts;
  foo() {ts = gcnew ThreadSafe();}
  double init() { return 0.0; }
  double add(int i, ParallelLoopState^, double d) { return d + 1.0; }
  void end(double d) { ts->AddToTotal(d); }
};

int main(array<System::String^>^args)
{
  foo^ f = gcnew foo();
  Parallel::For<double>(0,1000000,
    gcnew Func<double>(f, &foo::init),
    gcnew Func<int, ParallelLoopState^, double, double>(f, &foo::add),
    gcnew Action<double>(f, &f::end));
  Console::WriteLine(f->ts->Total);
}

I've seen it run with 1, 2 and 4 task threads. Always outputs 1000000 (for this case).

Pretty sure you didn't make a mistake in your ThreadSafe class. I'd start looking elsewhere.

With your code as written, numbers->Count will equal 0, which will cause the Parallel::For to never execute. I assume you've just omitted the code that fills up numbers , but if not, that would be one reason it wouldn't work.

Update: Here is an explanation of what I intended to do in my Parallel:For loop:
Each task starts at 0. Each call to Add adds 1 to the running amount for the task, then, once the task is finished, it writes out its total to the ThreadSafe class.

So, instead of calling AddToTotal 100 times, I'm only calling it once per task:

  • 1 task: AddToTotal called with a value of 100.
  • 2 tasks: AddToTotal called with 2 values that are approximately 50, summing to 100.
  • 4 tasks: AddToTotal called with 4 values that are approximately 25, summing to 100.

My add() function is still thread-safe, because it isn't accessing any values other than the input parameter d , which is either the value returned from init() (when the task was started) or the value from the previous add() for that task.

To prove my point, I added a Console::WriteLine(d) to my end() function and upped my count to 1000000. I ran it many times and my final count is always 1000000. Here's an abnormal example that still worked: 467922, 454959, 77119. Total 1000000. (first time I've seen 3 tasks).

Of course, now I've just thought about your concern that I'm only calling AddToTotal a few times and it may never be called simultaneously.

Here is my update to add() and end():

double add(int i, ParallelLoopState^, double d) { return bar->AddToTotal(1.0); }
void end(double d) { Console::WriteLine(d); }

Now, all the adds are done in the tasks and AddToTotal will be called 1000000 times. end() will only output the final number for each task, returned from AddToTotal in the last call to add() .

I still get 1000000 calls. I do get more tasks, likely because of all the calls to AddToTotal now.

So I agree. My first attempt wasn't a good proof that AddToTotal was thread-safe. Now, I expect that it is.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM