【问题标题】:C++\Cli Interlocked::Add for doubles - can't get Interlocked::CompareExchange alternative workingC++\Cli Interlocked::Add for doubles - 无法获得 Interlocked::CompareExchange 替代工作
【发布时间】:2014-05-28 13:30:33
【问题描述】:

这是一个后续问题,该问题解决了带有线程局部变量 Parallel::For with local variable 的 Parallel::For 语法

我尝试实现的书籍示例在整数上执行 Interlocked::Add,但我的实现需要双精度数。在上面引用的代码中,我实现了一个 Lock 例程,但这不起作用。所以我现在正在尝试将 C# 示例转换为 C++\Cli 类,然后我可以将其作为双精度的安全添加来调用 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;
}

然后我在包含 Parallel:For 例程的类中调用它,该例程也在上面引用的帖子中列出。

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);
}

代码编译并运行,但没有添加。它总是返回 0。

所以我假设我对 C# 示例的翻译/实现不正确。怎么了?

【问题讨论】:

  • 你读过 Raymond Chen 的博客上的An extensible interlocked arithmetic function 了吗?无需使用 lambda,但请务必了解发生了什么。
  • 感谢您的链接。然而,他也将 C++ 实现留给了读者。起点与我提到的第二个参考非常相似。无论如何,上述工作。

标签: .net multithreading parallel-processing c++-cli


【解决方案1】:

我用你的ThreadSafe 类做了一个非常简单的例子。

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);
}

我已经看到它使用 1、2 和 4 个任务线程运行。始终输出 1000000(对于这种情况)。

很确定您在ThreadSafe 课程中没有犯错。我会开始寻找其他地方。

使用您编写的代码,numbers-&gt;Count 将等于 0,这将导致 Parallel::For 永远不会执行。我假设您刚刚省略了填充 numbers 的代码,但如果没有,那将是它不起作用的原因之一。

更新:这是我打算在 Parallel:For 循环中执行的操作的说明:
每个任务从 0 开始。每次调用 Add 都会将 1 添加到任务的运行量中,然后,一旦任务完成,它会将其总数写入 ThreadSafe 类。

所以,我没有调用AddToTotal 100 次,而是每个任务只调用一次:

  • 1 个任务:调用 AddToTotal,值为 100。
  • 2 个任务:AddToTotal 调用了 2 个大约为 50 的值,总和为 100。
  • 4 个任务:AddToTotal 调用了 4 个值,大约为 25,总和为 100。

我的add() 函数仍然是线程安全的,因为它不访问除输入参数d 之外的任何值,这是从init()(任务启动时)返回的值或该任务的前一个 add() 的值。

为了证明我的观点,我在我的end() 函数中添加了一个Console::WriteLine(d) 并将我的计数提高到1000000。我运行了很多次,我的最终计数始终是1000000。这是一个仍然有效的异常示例:467922, 454959,77119。总共1000000。(我第一次看到3个任务)。

当然,现在我刚刚考虑到您的担忧,即我只给AddToTotal 打了几次电话,而且可能永远不会同时调用。

这是我对 add() 和 end() 的更新:

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

现在,所有添加都在任务中完成,AddToTotal 将被调用 1000000 次。 end() 只会输出每个任务的最终编号,在最后一次调用add() 时从AddToTotal 返回。

我仍然接到 1000000 个电话。我确实得到了更多的任务,可能是因为现在所有对AddToTotal 的调用。

所以我同意。我的第一次尝试并不能很好地证明AddToTotal 是线程安全的。现在,我希望它是。

【讨论】:

  • 感谢乔尔的努力! 'numbers->Count' 不为空。为了清楚起见,确实省略了代码。很高兴知道 Threadsafe 类有效,我需要进行更多搜索...
  • 看了你的例子。看起来您正在使用d + 1.0 添加add(),但这实际上应该在线程安全方法AddToTotal() 中完成。那么您可能看到的是 Add() 中的不安全添加,而 Threadsafe AddToTotal 实际上不起作用?
  • @jdelang 将我的测试更改为调用AddToTotal() 1000000 次。还没有失败。
  • 正确。您的示例程序指出了我的错误:在 computeSumScore 方法中,我只做了一个分配(tempVal = 1.0),而不是像您一样使用胎面局部变量进行实际安全添加。我将其更改为 tempVal += 1.0,然后它就可以工作了!我现在已经用这个求和和(精心制作的)外部序列比对和映射方法更新了我的程序中的方法,并将结果与​​旧的顺序计算进行了比较,我总是得到相同的结果。充分证明它有效。谢谢乔尔!
猜你喜欢
  • 2014-07-13
  • 2021-05-08
  • 2014-12-12
  • 2018-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多