web-dev-qa-db-fra.com

Le thread C # Random Number Generator est-il sûr?

La méthode Random.Next() de C # est-elle thread-safe?

65
noone

Il n'y a rien de spécial à faire dans la méthode Next pour assurer la sécurité des threads. Cependant, c'est une méthode d'instance. Si vous ne partagez pas d'instances de Random entre différents threads, vous n'avez pas à vous soucier de la corruption d'état au sein d'une instance. N'utilisez pas une seule instance de Random sur différents threads sans détenir un verrou exclusif quelconque.

Jon Skeet a quelques bons articles sur ce sujet:

StaticRandom
Revisiter l'aléatoire

Comme l'ont noté certains commentateurs, il existe un autre problème potentiel dans l'utilisation de différentes instances de Random qui sont exclusives aux threads, mais qui sont semées de manière identique et induisent donc les séquences identiques de nombres pseudo-aléatoires, car elles peuvent être créées au niveau en même temps ou à proximité temporelle étroite les uns des autres. Une façon de résoudre ce problème consiste à utiliser une instance principale Random (qui est verrouillée par un seul thread) pour générer des graines aléatoires et initialiser de nouvelles instances Random pour chaque autre thread à utiliser.

25
Mehrdad Afshari

Non, l'utilisation de la même instance à partir de plusieurs threads peut provoquer sa rupture et renvoyer tous les 0. Cependant, créer une version thread-safe (sans avoir besoin de verrous désagréables à chaque appel à Next()) est simple. Adapté de l'idée de cet article :

public class ThreadSafeRandom
{
    private static readonly Random _global = new Random();
    [ThreadStatic] private static Random _local;

    public int Next()
    {
        if (_local == null)
        {
            lock (_global)
            {
                if (_local == null)
                {
                    int seed = _global.Next();
                    _local = new Random(seed);
                }
            }
        }

        return _local.Next();
    }
}

L'idée est de garder un static Random variable pour chaque thread. Cependant, cela échoue de manière évidente, en raison d'un autre problème avec Random - si plusieurs instances sont créées presque en même temps (dans un délai d'environ 15 ms) , ils renverront tous les mêmes valeurs! Pour résoudre ce problème, nous créons une instance Random globalement statique pour générer les graines utilisées par chaque thread.

Soit dit en passant, l'article ci-dessus contient du code illustrant ces deux problèmes avec Random.

La réponse officielle de Microsoft est très forte non . De http://msdn.Microsoft.com/en-us/library/system.random.aspx#8 :

Les objets aléatoires ne sont pas thread-safe. Si votre application appelle des méthodes aléatoires à partir de plusieurs threads, vous devez utiliser un objet de synchronisation pour vous assurer qu'un seul thread peut accéder au générateur de nombres aléatoires à la fois. Si vous ne vous assurez pas que l'objet Random est accessible de manière thread-safe, les appels aux méthodes qui renvoient des nombres aléatoires renvoient 0.

Comme décrit dans la documentation, il y a un effet secondaire très désagréable qui peut se produire lorsque le même objet Random est utilisé par plusieurs threads: il cesse de fonctionner.

(c'est-à-dire qu'il existe une condition de concurrence critique qui, lorsqu'elle est déclenchée, renvoie la valeur des méthodes `` random.Next .... '' sera 0 pour tous les appels suivants.)

17
JSWork

Non, ce n'est pas sûr pour les threads. Si vous devez utiliser la même instance à partir de différents threads, vous devez synchroniser l'utilisation.

Je ne vois pas vraiment pourquoi vous en auriez besoin. Il serait plus efficace pour chaque thread d'avoir sa propre instance de la classe Random.

14
Guffa

Un autre moyen sûr pour les threads est d'utiliser ThreadLocal<T> Comme suit:

new ThreadLocal<Random>(() => new Random(GenerateSeed()));

La méthode GenerateSeed() devra renvoyer une valeur unique à chaque fois qu'elle sera appelée pour garantir que les séquences de nombres aléatoires sont uniques dans chaque thread.

static int SeedCount = 0;
static int GenerateSeed() { 
    return (int) ((DateTime.Now.Ticks << 4) + 
                   (Interlocked.Increment(ref SeedCount))); 
}

Fonctionne pour un petit nombre de threads.

8
rjroy

Puisque Random n'est pas adapté aux threads, vous devez en avoir un par thread, plutôt qu'une instance globale. Si vous craignez que ces multiples classes Random soient amorcées en même temps (c'est-à-dire par DateTime.Now.Ticks ou autre), vous pouvez utiliser Guids pour amorcer chacun d'eux. Le générateur .NET Guid va très loin pour garantir des résultats non reproductibles, d'où:

var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
5
Glenn Slayden

Pour ce que ça vaut, voici un RNG thread-safe, cryptographiquement fort qui hérite de Random.

L'implémentation comprend des points d'entrée statiques pour faciliter l'utilisation, ils ont les mêmes noms que les méthodes d'instance publique mais sont préfixés avec "Get".

Un appel au RNGCryptoServiceProvider.GetBytes est une opération relativement coûteuse. Ceci est atténué par l'utilisation d'un tampon interne ou "Pool" pour rendre l'utilisation moins fréquente et plus efficace de RNGCryptoServiceProvider. S'il y a peu de générations dans un domaine d'application, cela peut être considéré comme une surcharge.

using System;
using System.Security.Cryptography;

public class SafeRandom : Random
{
    private const int PoolSize = 2048;

    private static readonly Lazy<RandomNumberGenerator> Rng =
        new Lazy<RandomNumberGenerator>(() => new RNGCryptoServiceProvider());

    private static readonly Lazy<object> PositionLock =
        new Lazy<object>(() => new object());

    private static readonly Lazy<byte[]> Pool =
        new Lazy<byte[]>(() => GeneratePool(new byte[PoolSize]));

    private static int bufferPosition;

    public static int GetNext()
    {
        while (true)
        {
            var result = (int)(GetRandomUInt32() & int.MaxValue);

            if (result != int.MaxValue)
            {
                return result;
            }
        }
    }

    public static int GetNext(int maxValue)
    {
        if (maxValue < 1)
        {
            throw new ArgumentException(
                "Must be greater than zero.",
                "maxValue");
        }
        return GetNext(0, maxValue);
    }

    public static int GetNext(int minValue, int maxValue)
    {
        const long Max = 1 + (long)uint.MaxValue;

        if (minValue >= maxValue)
        {
            throw new ArgumentException(
                "minValue is greater than or equal to maxValue");
        }

        long diff = maxValue - minValue;
        var limit = Max - (Max % diff);

        while (true)
        {
            var Rand = GetRandomUInt32();
            if (Rand < limit)
            {
                return (int)(minValue + (Rand % diff));
            }
        }
    }

    public static void GetNextBytes(byte[] buffer)
    {
        if (buffer == null)
        {
            throw new ArgumentNullException("buffer");
        }

        if (buffer.Length < PoolSize)
        {
            lock (PositionLock.Value)
            {
                if ((PoolSize - bufferPosition) < buffer.Length)
                {
                    GeneratePool(Pool.Value);
                }

                Buffer.BlockCopy(
                    Pool.Value,
                    bufferPosition,
                    buffer,
                    0,
                    buffer.Length);
                bufferPosition += buffer.Length;
            }
        }
        else
        {
            Rng.Value.GetBytes(buffer);
        }
    }

    public static double GetNextDouble()
    {
        return GetRandomUInt32() / (1.0 + uint.MaxValue);
    }

    public override int Next()
    {
        return GetNext();
    }

    public override int Next(int maxValue)
    {
        return GetNext(0, maxValue);
    }

    public override int Next(int minValue, int maxValue)
    {
        return GetNext(minValue, maxValue);
    }

    public override void NextBytes(byte[] buffer)
    {
        GetNextBytes(buffer);
    }

    public override double NextDouble()
    {
        return GetNextDouble();
    }

    private static byte[] GeneratePool(byte[] buffer)
    {
        bufferPosition = 0;
        Rng.Value.GetBytes(buffer);
        return buffer;
    }

    private static uint GetRandomUInt32()
    {
        uint result;
        lock (PositionLock.Value)
        {
            if ((PoolSize - bufferPosition) < sizeof(uint))
            {
                GeneratePool(Pool.Value)
            }

            result = BitConverter.ToUInt32(
                Pool.Value,
                bufferPosition);
            bufferPosition+= sizeof(uint);
        }

        return result;
    }
}
4
Jodrell

Par documentation

Tous les membres statiques publics (partagés en Visual Basic) de ce type sont thread-safe. Les membres d'instance ne sont pas garantis pour être thread-safe.

http://msdn.Microsoft.com/en-us/library/system.random.aspx

2
Seattle Leonard

Pour un générateur de nombres aléatoires sûr pour les threads, regardez RNGCryptoServiceProvider . De la documentation:

Sécurité des fils

Ce type est thread-safe.

1
Hannoun Yassir