Boost数学(ibeta_inv函数)不是线程安全的

本文关键字:线程 安全 函数 数学 ibeta inv Boost | 更新日期: 2023-09-27 18:29:19

我已经将boost的一部分-ibeta_inv函数-编译成了一个.Net 64位程序集,它运行得很好,直到我开始从多个线程调用它。然后它偶尔会返回错误的结果。

我使用以下代码(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));
}

更新:正如我在下面的评论中所看到的,我再也不确定这是Boost问题了。也许是一些奇怪的PLinq到C++/CLI的错误???我很生气,稍后会带着更多的事实回来。

Boost数学(ibeta_inv函数)不是线程安全的

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

锁定版本:(请参阅此问题以确定哪一个最适合您的应用程序)

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

我碰巧在一个C++/CLI 64位项目中封装了boost的一部分,并像您一样从C#中使用它。

因此,我在自己的Boost包装器中加入了您的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;
        }
    }

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

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

我还注意到,您将结果称为"BoostResult"。。。并在评论中提到一些关于"我们目前的执行情况"的内容。你到底在和什么结果比较?你对"正确"的定义是什么?