2012-03-27 108 views
8

我已经将boost的一部分 - ibeta_inv函数编译到了一个.Net 64位程序集中,它运行良好,直到我开始从多线程调用它。然后它偶尔返回错误的结果。Boost数学(ibeta_inv函数)不是线程安全的?

我遵守它使用此代码(C++/CLI):

// Boost.h 

#pragma once 

#include <boost/math/special_functions/beta.hpp> 

using namespace boost::math; 

namespace Boost { 

    public ref class BoostMath 
    { 
    public: 
     double static InverseIncompleteBeta(double a, double b, double x) 
     { 
      return ibeta_inv(a,b,x); 
     } 
    }; 
} 

有没有人尝试过呢?

我没有试过这个.Net,所以我不知道这是不是原因,但我真的不明白为什么,因为它的工作原理很棒单线程。

用法(C#):

private void calcBoost(List<Val> vals) 
{ 
    //gives WRONG results (sometimes): 
    vals.AsParallel().ForAll(v => v.BoostResult = BoostMath.InverseIncompleteBeta(v.A, v.B, v.X)); 
    //gives CORRECT results: 
    vals.ForEach(v => v.BoostResult = BoostMath.InverseIncompleteBeta(v.A, v.B, v.X)); 
} 

UPDATE:可以看出在下面我的意见 - 我不知道在所有了,这是一个加速的问题。也许这是一些奇怪的PLinq到C++/CLI bug?我很忙,并且会在稍后回复更多的事实。

+2

文档说整个boost.math应该是线程安全的,只要您使用内置的浮点类型(正如我所看到的那样)。也许你应该提交一个bug? http://www.boost.org/doc/libs/release/libs/math/doc/sf_and_dist/html/math_toolkit/main_overview/threads.html – stanwise 2012-03-27 12:08:40

+0

如果没有其他东西出现,我可以在本机C++中尝试应用程序,看看问题是否仍然存在。如果是这样,一个错误报告可能是唯一要做的事情,因为我无法在源代码中找到任何东西。虽然可惜......它的运行速度是我们目前实现的反向不完全测试函数的两倍。 – 2012-03-27 12:12:48

+0

有趣!想到两个想法:(1)三重检查你已经在多线程模式下建立了提升(当前版本仍然有区别),(2)这个引用来自@stanwise linked文档:'后一个限制的原因是需要使用结构来初始化符号常量......因为在这种情况下,需要运行T的构造函数,导致潜在的竞争条件。“我公开怀疑你的代码是否意外地暴露了这种竞争条件,并且我全心全意地回来stanwise报告这是一个错误。 – MrGomez 2012-04-02 22:11:24

回答

2

我碰巧在C++/CLI 64bit项目中封装了boost的一部分,并且完全按照您的方式在C#中使用它。

所以,我在你的C++类扔在我自己的升压的包装,并将此代码到C#项目:

private class Val 
    { 
     public double A; 
     public double B; 
     public double X; 
     public double ParallellResult; 
    } 

    private static void findParallelError() 
    { 
     var r = new Random(); 

     while (true) 
     { 
      var vals = new List<Val>(); 
      for (var i = 0; i < 1000*1000; i++) 
      { 
       var val = new Val(); 
       val.A = r.NextDouble()*100; 
       val.B = val.A + r.NextDouble()*1000; 
       val.X = r.NextDouble(); 
       vals.Add(val); 
      } 

      // parallel calculation 
      vals.AsParallel().ForAll(v => v.ParallellResult = Boost.BoostMath.InverseIncompleteBeta(v.A, v.B, v.X)); 

      /sequential verification 
      var error = vals.Exists(v => v.ParallellResult != Boost.BoostMath.InverseIncompleteBeta(v.A, v.B, v.X)); 
      if (error) 
       return; 
     } 
    } 

它只是执行“永远”。平行结果始终等于顺序结果。这里没有线程不安全的...

我可以建议你下载一个新的副本升压并将其包含在一个全新的项目中,并尝试一下吗?

我还注意到,你称你的结果为“BoostResult”......并在评论中提到了有关“我们当前的实现”的内容。究竟是什么你比较你的结果再次?你对“正确”的定义是什么?

+0

Bonkers。我必须为这个问题写下道歉。它基于完全错误的假设。可以说,对墨菲法律的强大性表示敬意。错误在于我试图加速的生产wcode。 – 2012-04-07 13:59:12

4

Val类是线程安全的,这一点至关重要。

一个简单的方法来确保这将是使其不变,但我看到你也有BoostResult需要写。所以这需要是volatile,或者有某种形式的锁定。

public sealed class Val 
{ 
    // Immutable fields are inheriently threadsafe 
    public readonly double A; 
    public readonly double B; 
    public readonly double X; 

    // volatile is an easy way to make a single field thread safe 
    // box and unbox to allow double as volatile 
    private volatile object boostResult = 0.0; 

    public Val(double A, double B, double X) 
    { 
     this.A = A; 
     this.B = B; 
     this.X = X; 
    } 

    public double BoostResult 
    { 
     get 
     { 
      return (double)boostResult; 
     } 
     set 
     { 
      boostResult = value; 
     } 
    } 
} 

锁版:(见this question,以确定哪些是最适合你的应用程序)

public sealed class Val 
{ 
    public readonly double A; 
    public readonly double B; 
    public readonly double X; 

    private readonly object lockObject = new object(); 
    private double boostResult; 

    public Val(double A, double B, double X) 
    { 
     this.A = A; 
     this.B = B; 
     this.X = X; 
    } 

    public double BoostResult 
    { 
     get 
     { 
      lock (lockObject) 
      { 
       return boostResult; 
      } 
     } 
     set 
     { 
      lock (lockObject) 
      { 
       boostResult = value; 
      } 
     } 
    } 
} 

如果你觉得600万个锁将是缓慢的,只是试试这个:

using System; 

namespace ConsoleApplication17 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      { //without locks 
       var startTime = DateTime.Now; 
       int i2=0; 
       for (int i = 0; i < 6000000; i++) 
       { 
        i2++; 
       } 
       Console.WriteLine(i2); 
       Console.WriteLine(DateTime.Now.Subtract(startTime)); //0.01 seconds on my machine 
      } 
      { //with locks 
       var startTime = DateTime.Now; 
       var obj = new Object(); 
       int i2=0; 
       for (int i = 0; i < 6000000; i++) 
       { 
        lock (obj) 
        { 
         i2++; 
        } 
       } 
       Console.WriteLine(i2); 
       Console.WriteLine(DateTime.Now.Subtract(startTime)); //0.14 seconds on my machine, and this isn't even in parallel. 
      } 
      Console.ReadLine(); 
     } 
    } 
} 
+0

不,这是不正确的。使用第一版 - 我仍然遇到同样的问题(并且double不能变化,但这是无关紧要的,因为直到所有计算完成为止我都没有读取它)。要求600万次,如果我每次都要锁定 - 我们目前的实现速度会更快。但是:马王Val结构工程! – 2012-04-03 11:34:54

+0

@danbystrom噢,对不起。已更新易失版本。除非你对你的struct版本感到满意,否则如果新的volatile版本有效,我会感兴趣。你可能不会读它,但如果它是由主线程编写的,即使在'新Val'初始化期间,它也可以被我相信的那个线程缓存。 – weston 2012-04-03 12:20:28

+1

我不认为你有这个不稳定的概念是正确的。它告诉编译器,编译器不能生成将变量缓存在寄存器中的代码 - 因为它可以随时更改。它没有更深刻的“线程安全”测量。至少我不知道...随时纠正我!而且,不,我不喜欢我的结构解决方案......直到我知道那些疯狂的人正在发生什么!但是它鼓励我相信我能做到这一点......不知何故!不确定如果它不是您的评论,我会尝试结构的东西! – 2012-04-03 12:30:56