为什么每个人都指出,自旋锁更快? [英] Why everyone states that SpinLock is faster?

查看:184
本文介绍了为什么每个人都指出,自旋锁更快?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经读了很多文档和文章和帖子遍布网络。
几乎每个人都到处提交了自旋锁是短期运行的代码块快,但我做了一个试验,这在我看来,简单的Monitor.Enter作品比SpinLock.Enter快(测试是针对.NET编译4.5)

 使用系统; 
使用System.Collections.Concurrent;
使用System.Collections.Generic;使用System.Diagnostics程序
;使用System.Threading.Tasks
;
使用System.Linq的;
使用System.Globalization;
使用System.ComponentModel;
使用的System.Threading;使用的System.Net.Sockets
;使用System.Net
;

类节目
{
静态INT _loopsCount = 1000000;
静态INT _threadsCount = -1;

静态ProcessPriorityClass _processPriority = ProcessPriorityClass.RealTime;
静态ThreadPriority _threadPriority = ThreadPriority.Highest;

静态长_testingVar = 0;


静态无效的主要(字串[] args)
{
_threadsCount = Environment.ProcessorCount;

Console.WriteLine(核/处理器数:{0},Environment.ProcessorCount);

Process.GetCurrentProcess()的priorityClass = _processPriority。

时间跨度tsInterlocked = ExecuteInterlocked();
时间跨度tsSpinLock = ExecuteSpinLock();
时间跨度tsMonitor = ExecuteMonitor();

Console.WriteLine(与联锁试验:{0} ms\r\\\
Test用环形锁:{1} ms\r\\\
Test与显示器:{2}毫秒,
tsInterlocked.TotalMilliseconds,
tsSpinLock.TotalMilliseconds,
tsMonitor.TotalMilliseconds);

到Console.ReadLine();
}

静态时间跨度ExecuteInterlocked()
{
_testingVar = 0;

ManualResetEvent的_startEvent =新的ManualResetEvent(假);
CountdownEvent _endCountdown =新CountdownEvent(_threadsCount);

螺纹[]线程=新主题[_threadsCount]

的for(int i = 0; I< threads.Length;我++)
{
线由[i] =新的Thread(()=>
{
_startEvent.WaitOne();

为(INT J = 0; J< _loopsCount; J ++)
{
Interlocked.Increment(REF _testingVar);
}

_endCountdown.Signal();
});

线程[I] .Priority = _threadPriority;
线程[I]。开始();
}

秒表SW = Stopwatch.StartNew();

_startEvent.Set();
_endCountdown.Wait();

返回sw.Elapsed;
}

静态自旋锁_spinLock =新的自旋锁();

静态时间跨度ExecuteSpinLock()
{
_testingVar = 0;

ManualResetEvent的_startEvent =新的ManualResetEvent(假);
CountdownEvent _endCountdown =新CountdownEvent(_threadsCount);

螺纹[]线程=新主题[_threadsCount]

的for(int i = 0; I< threads.Length;我++)
{
线由[i] =新的Thread(()=>
{
_startEvent.WaitOne();

布尔lockTaken;

为(INT J = 0; J< _loopsCount; J ++)
{
lockTaken = FALSE;


{
_spinLock.Enter(REF lockTaken);

_testingVar ++;
}
终于
{
如果(lockTaken)
{
_spinLock.Exit();
}
}
}

_endCountdown.Signal();
});

线程[I] .Priority = _threadPriority;
线程[I]。开始();
}

秒表SW = Stopwatch.StartNew();

_startEvent.Set();
_endCountdown.Wait();

返回sw.Elapsed;
}

静态对象_locker =新的对象();

静态时间跨度ExecuteMonitor()
{
_testingVar = 0;

ManualResetEvent的_startEvent =新的ManualResetEvent(假);
CountdownEvent _endCountdown =新CountdownEvent(_threadsCount);

螺纹[]线程=新主题[_threadsCount]

的for(int i = 0; I< threads.Length;我++)
{
线由[i] =新的Thread(()=>
{
_startEvent.WaitOne();

布尔lockTaken;

为(INT J = 0; J< _loopsCount; J ++)
{
lockTaken = FALSE;


{
Monitor.Enter(_locker,楼盘lockTaken);

_testingVar ++;
}
终于
{
如果(lockTaken)
{
Monitor.Exit(_locker);
}
}
$} b
$ b _endCountdown.Signal();
});

线程[I] .Priority = _threadPriority;
线程[I]。开始();
}

秒表SW = Stopwatch.StartNew();

_startEvent.Set();
_endCountdown.Wait();

返回sw.Elapsed;
}
}

在2.5 GHz的这个应用程序的24个核心的服务器使用x64编译产生以下结果:

 核心/处理器数:24 
测试与联锁:1373.0829毫秒
测试与自旋锁:10894.6283毫秒
测试与监控:1171.1591毫秒


解决方案

您只是没有测试场景,其中自旋锁可以提高线程。后面的自旋锁的核心思想是,一个线程上下文切换是非常昂贵的操作,2000和10000 CPU周期之间成本核算。而且,如果它很可能是一个线程可以等待一个位(纺)获取锁定,则多余的周期烧毁等待可以通过避免线程上下文切换还清。



所以,基本要求是,锁持有很短的时间,这是真的你的情况。并有该锁能够获得合理的可能性。这是不是在你的情况属实,锁的按不低于24线程的兵家必争之地。所有纺纱而不必机会获得锁燃烧核心。



在此测试显示器将工作最好的,因为它会将线程正在等待获取锁。他们被暂停,直到其中一人有一个的机会的锁被释放时获取锁,从等待队列释放。给他们所有的公平的机会来转一转,从而最大限度地赔率,他们将完成所有在同一时间。 Interlocked.Increment也不错,但不能提供一个公平的保障。



它可以是非常难以判断是否自旋锁是正确的做法前面,你必须测量。并发分析是正确的一种工具。


I have read a lot of docs and articles and posts all over the internet. Almost everyone and everywhere commits that SpinLock is faster for a short running pieces of code, but I made a test, and it appears to me that simple Monitor.Enter works faster than SpinLock.Enter (Test is compiled against .NET 4.5)

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Linq;
using System.Globalization;
using System.ComponentModel;
using System.Threading;
using System.Net.Sockets;
using System.Net;

class Program
{
    static int _loopsCount = 1000000;
    static int _threadsCount = -1;

    static ProcessPriorityClass _processPriority = ProcessPriorityClass.RealTime;
    static ThreadPriority _threadPriority = ThreadPriority.Highest;

    static long _testingVar = 0;


    static void Main(string[] args)
    {
        _threadsCount = Environment.ProcessorCount;

        Console.WriteLine("Cores/processors count: {0}", Environment.ProcessorCount);

        Process.GetCurrentProcess().PriorityClass = _processPriority;

        TimeSpan tsInterlocked = ExecuteInterlocked();
        TimeSpan tsSpinLock = ExecuteSpinLock();
        TimeSpan tsMonitor = ExecuteMonitor();

        Console.WriteLine("Test with interlocked: {0} ms\r\nTest with SpinLock: {1} ms\r\nTest with Monitor: {2} ms",
            tsInterlocked.TotalMilliseconds,
            tsSpinLock.TotalMilliseconds,
            tsMonitor.TotalMilliseconds);

        Console.ReadLine();
    }

    static TimeSpan ExecuteInterlocked()
    {
        _testingVar = 0;

        ManualResetEvent _startEvent = new ManualResetEvent(false);
        CountdownEvent _endCountdown = new CountdownEvent(_threadsCount);

        Thread[] threads = new Thread[_threadsCount];

        for (int i = 0; i < threads.Length; i++)
        {
            threads[i] = new Thread(() =>
                {
                    _startEvent.WaitOne();

                    for (int j = 0; j < _loopsCount; j++)
                    {
                        Interlocked.Increment(ref _testingVar);
                    }

                    _endCountdown.Signal();
                });

            threads[i].Priority = _threadPriority;
            threads[i].Start();
        }

        Stopwatch sw = Stopwatch.StartNew();

        _startEvent.Set();
        _endCountdown.Wait();

        return sw.Elapsed;
    }

    static SpinLock _spinLock = new SpinLock();

    static TimeSpan ExecuteSpinLock()
    {
        _testingVar = 0;

        ManualResetEvent _startEvent = new ManualResetEvent(false);
        CountdownEvent _endCountdown = new CountdownEvent(_threadsCount);

        Thread[] threads = new Thread[_threadsCount];

        for (int i = 0; i < threads.Length; i++)
        {
            threads[i] = new Thread(() =>
            {
                _startEvent.WaitOne();

                bool lockTaken;

                for (int j = 0; j < _loopsCount; j++)
                {
                    lockTaken = false;

                    try
                    {
                        _spinLock.Enter(ref lockTaken);

                        _testingVar++;
                    }
                    finally
                    {
                        if (lockTaken)
                        {
                            _spinLock.Exit();
                        }
                    }
                }

                _endCountdown.Signal();
            });

            threads[i].Priority = _threadPriority;
            threads[i].Start();
        }

        Stopwatch sw = Stopwatch.StartNew();

        _startEvent.Set();
        _endCountdown.Wait();

        return sw.Elapsed;
    }

    static object _locker = new object();

    static TimeSpan ExecuteMonitor()
    {
        _testingVar = 0;

        ManualResetEvent _startEvent = new ManualResetEvent(false);
        CountdownEvent _endCountdown = new CountdownEvent(_threadsCount);

        Thread[] threads = new Thread[_threadsCount];

        for (int i = 0; i < threads.Length; i++)
        {
            threads[i] = new Thread(() =>
            {
                _startEvent.WaitOne();

                bool lockTaken;

                for (int j = 0; j < _loopsCount; j++)
                {
                    lockTaken = false;

                    try
                    {
                        Monitor.Enter(_locker, ref lockTaken);

                        _testingVar++;
                    }
                    finally
                    {
                        if (lockTaken)
                        {
                            Monitor.Exit(_locker);
                        }
                    }
                }

                _endCountdown.Signal();
            });

            threads[i].Priority = _threadPriority;
            threads[i].Start();
        }

        Stopwatch sw = Stopwatch.StartNew();

        _startEvent.Set();
        _endCountdown.Wait();

        return sw.Elapsed;
    }
}

On a server with 24 cores of 2.5 GHz this application compiled with x64 produced the following results:

Cores/processors count: 24
Test with interlocked: 1373.0829 ms
Test with SpinLock: 10894.6283 ms
Test with Monitor: 1171.1591 ms

解决方案

You are just not testing a scenario where SpinLock can improve the threading. The core idea behind a spin-lock is that a thread-context switch is very expensive operation, costing between 2000 and 10,000 cpu cycles. And that if it is likely that a thread can acquire a lock by waiting for a bit (spinning) then the extra cycles burned waiting can pay off by avoiding the thread context switch.

So basic requirements is that the lock is held for a very short time, which is true in your case. And that there are reasonable odds that the lock can be acquired. Which is not true in your case, the lock is heavily contested by no less than 24 threads. All spinning and burning core without having a chance to acquire the lock.

In this test Monitor will work best since it queues threads waiting to acquire the lock. They are suspended until one of them has a chance to acquire the lock, released from the wait queue when the lock is released. Giving them all a fair chance to take a turn, thus maximizing the odds that they'll all finish at the same time. Interlocked.Increment is not bad either but can't provide a fairness guarantee.

It can be pretty hard to judge whether Spinlock is the right approach up front, you have to measure. A concurrency analyzer is the right kind of tool.

这篇关于为什么每个人都指出,自旋锁更快?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆