这是对.NET的System.Threading.SpinLockSystem.Threading.SpinLock的有效和安全使用吗?
我为什么要这么做?
Random's的公共方法并不是线程安全的。我可以从运行时才知道的任何线程调用它们;它们来自线程池,并且可以被相当频繁地调用(最高每秒100次),所以我不想每次创建一个新的Random对象。
public static class SingleRandom
{
private static Random random;
private static SpinLock spinLock;
static SingleRandom()
{
random = new Random();
spinLock = new SpinLock();
}
public static int Next()
{
bool gotLock = false;
spinLock.Enter(ref gotLock);
int rv = random.Next();
if(gotLock)
spinLock.Exit(false); // ASSUMPTION not IA64, TODO what if ARM?
return rv;
}
public static int Next(int min, int max)
{
bool gotLock = false;
spinLock.Enter(ref gotLock);
int rv = random.Next(min, max);
if (gotLock)
spinLock.Exit(false); // ASSUMPTION not IA64, TODO what if ARM?
return rv;
}
public static double NextDouble()
{
bool gotLock = false;
spinLock.Enter(ref gotLock);
double rv = random.NextDouble();
if (gotLock)
spinLock.Exit(false); // ASSUMPTION not IA64, TODO what if ARM?
return rv;
}
}发布于 2013-12-03 05:06:59
SingleRandom是个非常糟糕的名字。Single在C#上下文中的意思是float,因此您的名称意味着生成float值的是Random,而这不是真。ThreadSafeRandom或SynchronizedRandom是更好的命名的例子。SpinLock会以任何方式提高性能吗?不知何故,我认为一个简单的lock会同样快,同时更容易读懂。公共静态int Next() { lock (随机){返回random.Next();}static readonly字段。发布于 2013-12-03 08:45:41
首先,重新迭代在另一个答案中提出的观点:
创建一个具有内部状态static的助手类是个坏主意。你失去了很多的灵活性,可重用性,增加了测试的痛苦,却一无所获。
静态类的主要问题是:您不能轻松地在单元测试中模拟它。如果您想要单元测试一些使用随机数的东西,那么能够给它提供一个已知的随机数序列是非常有帮助的,但是通过一个静态类,您开始添加用于测试的代码,如果您忘记将全局状态重置到一个已知的点,那么测试就会变得脆弱。调用new SingleRandom()很难--如果您认为这是一个问题,那么OO语言可能是错误的选择。
具有状态的静态类实际上是一个反模式的单例类。因此,你的问题不应该是“什么是令人信服的理由不使用它”,而是“什么是令人信服的理由使用它”。
我的另一个要点是:
当我读到这篇文章时,我的第一个想法是:早期优化的经典案例。
我将您的实现更改为使用标准.NET锁定:
public static class StandardLockSingleRandom
{
private static Random random = new Random();
private static object _lock = new object();
public static int Next()
{
lock (_lock)
{
return random.Next();
}
}
public static int Next(int min, int max)
{
lock (_lock)
{
return random.Next(min, max);
}
}
public static double NextDouble()
{
lock (_lock)
{
return random.NextDouble();
}
}
}该实现比您的代码少30%,代码也就少了很多。所以你的旋转锁最好多加些不错的表现。
让我们编写一些快速基准测试:
class Program
{
static void Main(string[] args)
{
const int numRandomCalls = 100000;
const int numTestLoops = 100;
for (int p = 1; p <= 16; p <<= 1)
{
Console.WriteLine("=========================================================================================================");
Console.WriteLine("Parallelism = {0}, Count of random numbers per iteration {1}", p, numRandomCalls);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = p };
RunAndMeasure("Spin Locked", numTestLoops, () => { Parallel.For(0, numRandomCalls, parallelOptions, (idx) => { SingleRandom.Next(); }); });
RunAndMeasure("Standard Locked", numTestLoops, () => { Parallel.For(0, numRandomCalls, parallelOptions, (idx) => { StandardLockSingleRandom.Next(); }); });
}
}
private static void RunAndMeasure(string name, int numLoops, Action act)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
for (int i = 0; i < numLoops; ++i)
{
act();
}
stopWatch.Stop();
var total = stopWatch.Elapsed.TotalMilliseconds;
var perIter = total / numLoops;
Console.WriteLine("{0}: # Test Loops = {1}, total time {2:.000}ms, {3:.000}ms per iteration", name, numLoops, total, perIter);
}
}基本上:
在我的机器上,它是一个启用了超线程的i7 (因此,在并行性方面远远超过8,可能不会有太多变化),结果是这个输出:
=========================================================================================
Parallelism = 1, Count of random numbers per iteration 100000
Spin Locked: # Test Loops = 100, total time 996.986ms, 9.970ms per iteration
Standard Locked: # Test Loops = 100, total time 482.924ms, 4.829ms per iteration
=========================================================================================
Parallelism = 2, Count of random numbers per iteration 100000
Spin Locked: # Test Loops = 100, total time 1144.200ms, 11.442ms per iteration
Standard Locked: # Test Loops = 100, total time 560.377ms, 5.604ms per iteration
=========================================================================================
Parallelism = 4, Count of random numbers per iteration 100000
Spin Locked: # Test Loops = 100, total time 1253.103ms, 12.531ms per iteration
Standard Locked: # Test Loops = 100, total time 601.836ms, 6.018ms per iteration
=========================================================================================
Parallelism = 8, Count of random numbers per iteration 100000
Spin Locked: # Test Loops = 100, total time 1592.358ms, 15.924ms per iteration
Standard Locked: # Test Loops = 100, total time 802.485ms, 8.025ms per iteration
=========================================================================================
Parallelism = 16, Count of random numbers per iteration 100000
Spin Locked: # Test Loops = 100, total time 1603.059ms, 16.031ms per iteration
Standard Locked: # Test Loops = 100, total time 811.937ms, 8.119ms per iteration因此,您的自旋锁实现要复杂得多,花费的时间是原来的两倍。
我们学到了什么:在你衡量事物之前,不要优化它们。
发布于 2013-12-03 09:32:41
由于您希望线程安全,所以只需使用ThreadLocal<Random>即可
public static class SingleRandom
{
private static ThreadLocal<Random> random;
static SingleRandom()
{
random = new ThreadLocal<Random>(() =>
{
return new Random();
});
}
public static int Next()
{
int rv = random.Value.Next();
return rv;
}
public static int Next(int min, int max)
{
int rv = random.Value.Next(min, max);
return rv;
}
public static double NextDouble()
{
double rv = random.Value.NextDouble();
return rv;
}
}还可以通过将Func传递给ThreadLocal构造函数来显式定义每个随机的种子
https://codereview.stackexchange.com/questions/36542
复制相似问题