Повысить математику (функция ibeta_inv) не безопасно для потоков? - PullRequest
8 голосов
/ 27 марта 2012

Я собрал часть boost - функцию ibeta_inv - в 64-битную сборку .Net, и она работала великолепно, пока я не начал вызывать ее из нескольких потоков. Тогда иногда возвращаются неверные результаты.

Я выполнил это с помощью этого кода (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 ??? Я задираюсь и вернусь с другими фактами позже.

Ответы [ 2 ]

4 голосов
/ 03 апреля 2012

Очень важно, чтобы класс 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;
            }
        }
    }
}

И если вы думаете, что 6 миллионов блокировок будут медленными, просто попробуйте это:

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();
        }
    }
}
2 голосов
/ 06 апреля 2012

Я случайно включил часть boost в 64-битный проект C ++ / CLI и использую его из C # точно так же, как вы.

Итак, я добавил ваш класс C ++ в свою собственную оболочку Boost и добавил этот код в проект 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" ... и упомянул в комментариях кое-что о "нашей текущей реализации". Что именно вы сравниваете свой результат против? Какое у вас определение "правильный"?

...