我的程序执行许多模拟,每个模拟都需要生成许多随机数。串行方法很简单并且有效。然而,在我追求工作并行化的过程中,我相信我创造了一种比我能找到的更直接的方法。其他方法有点过时,现在可能无法实现。
我错过了一些会让我的方法容易受到无数多线程问题影响的东西吗?我的方法使用Parallel.For
来为单个线程使用实例化变量的能力,因此它不像我找到的其他方法那样需要另一个类。在这种情况下,每个线程都有自己的Random
。
时间安排:
我的方法:4s
斯蒂芬:14岁 乔恩:16岁显然,我并不像斯蒂芬或乔恩那么了解所以我担心我错过了什么。
我的方法:
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) => { }
);
});
下一个方法是Stephen Toub在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();
}
});
});
下一个方法是Jon Skeet在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();
}
});
});
更新/回答:Brian指出我使用Jon的方法不正确。更正确的方法是为每个ThreadLocalRandom.Instance
循环调用Parallel.For
,并将该实例用于内部for
循环。这可以防止每个调用的线程检查,而每个Parallel.For
循环只有一个线程检查。正确使用Jon的方法使他的方法比我使用的Parallel.For
的重载更快。
答案 0 :(得分:4)
然而,在我追求工作并行化的过程中,我相信我发现了一种比我能找到的方法更直接的方法。
这更直接,但错了。
其他方法有点陈旧。
这到底意味着什么?
我是否遗漏了一些会使我的方法容易受到无数多线程问题影响的事情?
线程安全的最基本规则是:在没有锁定的情况下,不能在多个线程上使用not-threadsafe对象。 Random
不是线程安全的,但你在每个线程上使用相同的一个来计算种子。
请注意,Jon和Stephen的“过时”方法正确地锁定了种子随机。
显然,我不知道斯蒂芬或乔恩,所以我担心我错过了什么。
首先,在编写更多多线程代码之前,应该彻底内化线程安全的基本规则。
其次,你的态度是你的错误。正确的态度是:Jon和Stephen都是专家,他们的解决方案不包含不必要的部分。如果您认为自己找到的解决方案缺乏其解决方案的一部分,那么您需要解释为什么您的解决方案不需要其解决方案所具有的部分。
答案 1 :(得分:2)
您的代码更快,因为它更简单。您的代码为每个循环提供Random
的专用实例。 Jon和Stephen的代码也这样做,但在他们的代码中,每次访问Random
都必须检查正在使用的线程,然后拉出Random
的正确实例。斯蒂芬的代码比乔恩的代码更快,因为ThreadLocal
(ThreadStatic
的包装器)稍慢。
然而,关于他们的代码的好处是他们的代码提供了Random
的简单替代。您的方法将并行代码的责任放在初始化Random
上。在现实世界的问题中,与具有静态,线程安全的Random
服务相比,在各种支持函数中携带Random
的实例是一种麻烦。
在实际任务中,您的功能可能不会受到Random
调用的支配。因此,在正常情况下,代码的轻微性能损失是可以的。
我建议ThreadLocal<T>
超过ThreadStatic
(请参阅ThreadStatic v.s. ThreadLocal<T>: is generic better than attribute?进行讨论)。
顺便说一下,除了专用的锁定对象外,请不要使用lock
。像Jon(https://codeblog.jonskeet.uk/2008/12/05/redesigning-system-object-java-lang-object/)一样,我真的希望lock
甚至不支持任意对象。