1

My program performs many simulations, each of which calls for many random numbers to be generated. The serial method is straightforward and works. However, in my pursuit of parallelizing the work I believe I created a more straightforward method than what I could find. The other methods are somewhat dated and something might be possible now that wasn't possible then.

Am I missing something that will make my method susceptible to any of the myriad of multithreading problems? My method uses the ability of a Parallel.For to instantiate a variable for individual thread use and thus it doesn't require another class like the other methods I found. In this case each thread gets its own Random.

Timing:

My method: 4s

Stephen: 14s

Jon: 16s

Clearly I don't know as much as Stephen or Jon so I'm concerned I missed something.

My method:

Random rnd = new Random();
int trials = 1_000_000;

private readonly object globalLock = new object();
ParallelOptions po = new ParallelOptions();
po.MaxDegreeOfParallelism = 4;

await Task.Run(() =>
{
    Parallel.For<Random>(0, trials, po, 
    () => { lock(globalLock){ return new Random(rnd.Next()); } }, 
    (i, loop, local) =>
    {
        for (int work = 0; work < 1000; work++)
        {
            local.Next();
        }

        return local;
    },
        (x) => { }
    );
});

This next method is by Stephen Toub on the MSDN Blog:

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

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

await Task.Run(() =>
{
    Parallel.For(0, trials, i =>
    {
        for (int work = 0; work < 1000; work++)
        {
            RandomGen2.Next();
        }
    });

});

This next method is by Jon Skeet on his blog:

public static class ThreadLocalRandom
{
    private static readonly Random globalRandom = new Random();
    private static readonly object globalLock = new object();

    private static readonly ThreadLocal<Random> threadRandom = new ThreadLocal<Random>(NewRandom);

    public static Random NewRandom()
    {
        lock (globalLock)
        {
            return new Random(globalRandom.Next());
        }
    }

    public static Random Instance { get { return threadRandom.Value; } }

    public static int Next()
    {
        return Instance.Next();
    }
}

await Task.Run(() =>
{
    Parallel.For(0, trials, i =>
    {
        for (int work = 0; work < 1000; work++)
        {
            ThreadLocalRandom.Instance.Next();
        }
    });
});

Update/answer: Brian has pointed out that I was using Jon's method incorrectly. A more correct way would be to call an ThreadLocalRandom.Instance for each Parallel.For loop and use that instance for the internal for loop. This prevents the thread check on each call and instead there is only one thread check per Parallel.For loop. Using Jon's method correctly makes his method faster than the overload of Parallel.For that I was using.

4

2 回答 2

5

然而,在我追求并行化工作的过程中,我相信我找到了一种比我能找到的更直接的方法。

它更直接,但错误。

其他方法有些过时了。

这到底是什么意思?

我是否遗漏了一些会使我的方法容易受到无数多线程问题影响的东西?

线程安全最基本的规则是:不能在没有锁的情况下在多个线程上使用非线程安全对象。 Random不是线程安全的,但是您在每个线程上使用相同的线程来计算种子。

请注意,Jon 和 Stephen 的“过时”方法正确锁定了播种随机数。

显然我不像斯蒂芬或乔恩那样了解,所以我担心我错过了一些东西。

首先,在编写更多多线程代码之前,您应该彻底内化线程安全的基本规则。

其次,你的态度就是你的错误。正确的态度是:Jon 和 Stephen 都是专家,他们的解决方案不包含不必要的部分。如果您认为您找到的解决方案缺少他们的解决方案所具有的部分,那么您需要解释为什么您的解决方案不需要他们的解决方案所具有的部分

于 2018-01-24T23:31:46.300 回答
1

您的代码更快,因为它更简单。您的代码为每个循环提供了一个专用的Random. Jon 和 Stephen 的代码也是这样做的,但是在他们的代码中,每次访问都Random必须检查哪个线程正在使用,然后提取正确的Random. Stephen 的代码比 Jon 的代码快,因为ThreadLocal(它是一个 wrapper ThreadStatic)稍微慢一些。

然而,他们的代码的好处是他们的代码提供了一个简单的替代Random. 您的方法将责任放在要初始化的并行代码上Random。在现实世界的问题中,与拥有静态的、线程安全的服务Random相比,承载各种支持功能的实例有点麻烦。Random

在现实世界的任务中,您的功能可能不受对Random. 因此,在正常情况下,他们代码的轻微性能损失是可以的。

我推荐ThreadLocal<T>ThreadStatic(参见ThreadStatic vs ThreadLocal<T>: is generic better than attribute?进行讨论)。

顺便说一句,请不要lock与专用锁对象以外的任何东西一起使用。像乔恩(https://codeblog.jonskeet.uk/2008/12/05/redesigning-system-object-java-lang-object/)一样,我真的希望lock不支持任意对象。

于 2018-01-25T14:28:41.760 回答