首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >单例SpinLock:实现随机线程安全

单例SpinLock:实现随机线程安全
EN

Code Review用户
提问于 2013-12-03 01:41:37
回答 4查看 2.7K关注 0票数 6

这是对.NET的System.Threading.SpinLockSystem.Threading.SpinLock的有效和安全使用吗?

我为什么要这么做?Random's的公共方法并不是线程安全的。我可以从运行时才知道的任何线程调用它们;它们来自线程池,并且可以被相当频繁地调用(最高每秒100次),所以我不想每次创建一个新的Random对象。

代码语言:javascript
复制
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;
    }
}
EN

回答 4

Code Review用户

发布于 2013-12-03 05:06:59

  1. 对于你的班级来说,SingleRandom是个非常糟糕的名字。Single在C#上下文中的意思是float,因此您的名称意味着生成float值的是Random,而这不是真。ThreadSafeRandomSynchronizedRandom是更好的命名的例子。
  2. 您确定在您的案例中使用SpinLock会以任何方式提高性能吗?不知何故,我认为一个简单的lock会同样快,同时更容易读懂。公共静态int Next() { lock (随机){返回random.Next();}
  3. 我不确定让你的课保持静态是否是最好的方法。在我的经验中,在这种情况下,通常最好是创建一个非静态类,然后注入它的实例或初始化某个static readonly字段。
票数 6
EN

Code Review用户

发布于 2013-12-03 08:45:41

首先,重新迭代在另一个答案中提出的观点:

创建一个具有内部状态static的助手类是个坏主意。你失去了很多的灵活性,可重用性,增加了测试的痛苦,却一无所获。

静态类的主要问题是:您不能轻松地在单元测试中模拟它。如果您想要单元测试一些使用随机数的东西,那么能够给它提供一个已知的随机数序列是非常有帮助的,但是通过一个静态类,您开始添加用于测试的代码,如果您忘记将全局状态重置到一个已知的点,那么测试就会变得脆弱。调用new SingleRandom()很难--如果您认为这是一个问题,那么OO语言可能是错误的选择。

具有状态的静态类实际上是一个反模式的单例类。因此,你的问题不应该是“什么是令人信服的理由不使用它”,而是“什么是令人信服的理由使用它”。

我的另一个要点是:

当我读到这篇文章时,我的第一个想法是:早期优化的经典案例。

  • 你测量过产生随机数是瓶颈吗?
  • 你测量过你需要一个旋转锁吗?

我将您的实现更改为使用标准.NET锁定:

代码语言:javascript
复制
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%,代码也就少了很多。所以你的旋转锁最好多加些不错的表现。

让我们编写一些快速基准测试:

代码语言:javascript
复制
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);
    }
}

基本上:

  • 在不同并行度下获得10万个随机数
  • 运行每个测试100次,并构建平均

在我的机器上,它是一个启用了超线程的i7 (因此,在并行性方面远远超过8,可能不会有太多变化),结果是这个输出:

代码语言:javascript
复制
=========================================================================================
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

因此,您的自旋锁实现要复杂得多,花费的时间是原来的两倍。

我们学到了什么:在你衡量事物之前,不要优化它们。

票数 4
EN

Code Review用户

发布于 2013-12-03 09:32:41

由于您希望线程安全,所以只需使用ThreadLocal<Random>即可

代码语言:javascript
复制
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构造函数来显式定义每个随机的种子

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/36542

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档