C# 并行编程 之 自旋锁的使用

基本信息

如果持有锁的时间非常短,而且锁的粒度很精细,那么使用自旋锁会获得更好的性能。有时候,Monitor互斥锁的开销还是相当大的。但SpinLock 的与Monitor的使用形式还是基本类似的。

一段简单的代码示例:

using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Sample5_4_spinlock
{
    class Program
    {
        private static int _TaskNum = 3;
        private static Task[] _Tasks;
        private static StringBuilder _StrBlder;
        private const int RUN_LOOP = 50;

        private static SpinLock m_spinlock;

        private static void Work1(int TaskID)
        {
            int i = 0;
            string log = "";
            bool lockToken = false;
            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0}  Task : #{1}  Value: {2}  =====\n",
                                    DateTime.Now.TimeOfDay, TaskID, i);
                i++;
                try
                {
                    lockToken = false;
                    m_spinlock.Enter(ref lockToken);
                    _StrBlder.Append(log);
                }
                finally
                {
                    if (lockToken)
                        m_spinlock.Exit(false);
                }
            }
        }

        private static void Work2(int TaskID)
        {
            int i = 0;
            string log = "";
            bool lockToken = false;

            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0}  Task : #{1}  Value: {2}  *****\n",
                                    DateTime.Now.TimeOfDay, TaskID, i);
                i++;
                try
                {
                    lockToken = false;
                    m_spinlock.Enter(ref lockToken);
                    _StrBlder.Append(log);
                }
                finally
                {
                    if (lockToken)
                        m_spinlock.Exit(false);
                }
            }
        }

        private static void Work3(int TaskID)
        {
            int i = 0;
            string log = "";
            bool lockToken = false;

            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0}  Task : #{1}  Value: {2}  ~~~~~\n",
                                    DateTime.Now.TimeOfDay, TaskID, i);
                i++;
                try
                {
                    lockToken = false;
                    m_spinlock.Enter(ref lockToken);
                    _StrBlder.Append(log);
                }
                finally
                {
                    if (lockToken)
                        m_spinlock.Exit(false);
                }
            }
        }

        static void Main(string[] args)
        {
            _Tasks = new Task[_TaskNum];
            _StrBlder = new StringBuilder();
            m_spinlock = new SpinLock();

            _Tasks[0] = Task.Factory.StartNew((num) =>
            {
                var taskid = (int)num;
                Work1(taskid);
            }, 0);

            _Tasks[1] = Task.Factory.StartNew((num) =>
            {
                var taskid = (int)num;
                Work2(taskid);
            }, 1);

            _Tasks[2] = Task.Factory.StartNew((num) =>
            {
                var taskid = (int)num;
                Work3(taskid);
            }, 2);

            var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) =>
            {
                Task.WaitAll(_Tasks);
                Console.WriteLine("==========================================================");
                Console.WriteLine("All Phase is completed");
                Console.WriteLine("==========================================================");
                Console.WriteLine(_StrBlder);
            });

            try
            {
                finalTask.Wait();
            }
            catch (AggregateException aex)
            {
                Console.WriteLine("Task failed And Canceled" + aex.ToString());
            }
            finally
            {
            }
            Console.ReadLine();
        }
    }
}

在每个任务的finally块中,会调用SpinLock的release,否则在SpinLock.Enter中,程序会在循环中不断的尝试获得锁,造成死锁。一旦获得了锁,ref 的 LockToken会被变成 true。

使用超时

SpinLock 同样也提供了超时机制供开发使用。

一个程序示例,worker 1会造成超时,work 2 和work 3则在程序中捕获超时产生的异常,终止运行。

using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Sample5_5_spinlock_timeout
{
    class Program
    {
        private static int _TaskNum = 3;
        private static Task[] _Tasks;
        private static StringBuilder _StrBlder;
        private const int RUN_LOOP = 50;
        private static SpinLock m_spinlock;

        private static void Work1(int TaskID)
        {
            int i = 0;
            string log = "";
            bool lockToken = false;
            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0}  Task : #{1}  Value: {2}  =====\n",
                                  DateTime.Now.TimeOfDay, TaskID, i);
                i++;
                try
                {
                    lockToken = false;
                    m_spinlock.TryEnter(2000, ref lockToken);
                    if (!lockToken)
                    {
                        Console.WriteLine("Work1 TIMEOUT!! Will throw Exception");
                        throw new TimeoutException("Work1 TIMEOUT!!");
                    }
                    System.Threading.Thread.Sleep(5000);
                    _StrBlder.Append(log);
                }
                finally
                {
                    if (lockToken)
                        m_spinlock.Exit(false);
                }
            }
        }

        private static void Work2(int TaskID)
        {
            int i = 0;
            string log = "";
            bool lockToken = false;

            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0}  Task : #{1}  Value: {2}  *****\n",
                                  DateTime.Now.TimeOfDay, TaskID, i);
                i++;
                try
                {
                    lockToken = false;
                    m_spinlock.TryEnter(2000, ref lockToken);
                    if (!lockToken)
                    {
                        Console.WriteLine("Work2 TIMEOUT!! Will throw Exception");
                        throw new TimeoutException("Work2 TIMEOUT!!");
                    }

                    _StrBlder.Append(log);
                }
                finally
                {
                    if (lockToken)
                        m_spinlock.Exit(false);
                }
            }
        }

        private static void Work3(int TaskID)
        {
            int i = 0;
            string log = "";
            bool lockToken = false;

            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0}  Task : #{1}  Value: {2}  ~~~~~\n",
                                  DateTime.Now.TimeOfDay, TaskID, i);
                i++;
                try
                {
                    lockToken = false;
                    m_spinlock.TryEnter(2000, ref lockToken);
                    if (!lockToken)
                    {
                        Console.WriteLine("Work3 TIMEOUT!! Will throw Exception");
                        throw new TimeoutException("Work3 TIMEOUT!!");
                    }
                    _StrBlder.Append(log);
                }
                finally
                {
                    if (lockToken)
                        m_spinlock.Exit(false);
                }
            }
        }

        static void Main(string[] args)
        {
            _Tasks = new Task[_TaskNum];
            _StrBlder = new StringBuilder();
            m_spinlock = new SpinLock();

            _Tasks[0] = Task.Factory.StartNew((num) =>
            {
                var taskid = (int)num;
                Work1(taskid);
            }, 0);

            _Tasks[1] = Task.Factory.StartNew((num) =>
            {
                var taskid = (int)num;
                Work2(taskid);
            }, 1);

            _Tasks[2] = Task.Factory.StartNew((num) =>
            {
                var taskid = (int)num;
                Work3(taskid);
            }, 2);

            var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) =>
            {
                Task.WaitAll(_Tasks);
                Console.WriteLine("==========================================================");
                Console.WriteLine("All Phase is completed");
                Console.WriteLine("==========================================================");
                Console.WriteLine(_StrBlder);
            });

            try
            {
                finalTask.Wait();
            }
            catch (AggregateException aex)
            {
                Console.WriteLine("Task failed And Canceled" + aex.ToString());
            }
            finally
            {
            }
            Console.ReadLine();
        }

    }
}

使用基于自旋的等待

如果需要等待某个条件满足的时间很短,而且不希望发生上下文切换,基于自旋的【等待】是一种很好的解决方案。

  • SpinWait : 自旋等待
  • SpinUntil : 等待某个条件发生

如果发生了长时间的自旋,SpinWait会让出底层的时间片,并触发上下文切换。因为长时间的自旋会阻塞优先级更高的线程。当一个线程自旋时,它会将一个内核放入到一个繁忙的循环中,而且它不会让出处理器时间片的剩余部分。SpinWait的智能逻辑中会在自旋达到足够长的时间时停止自旋并让出处理器。当然可以考虑调用Thread.Sleep()方法,它会让出处理器时间,但开销比较大。

示例程序:这里通过使用SpinWait 来控制3个Task的执行顺序。

using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Sample5_6_spinwait
{
    class Program
    {
        private static int _TaskNum = 3;
        private static Task[] _Tasks;
        private static StringBuilder _StrBlder;
        private const int RUN_LOOP = 10;
        private static bool m_IsWork2Start = false;
        private static bool m_IsWork3Start = false;

        private static void Work1(int TaskID)
        {
            int i = 0;
            string log = "";

            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0}  Task : #{1}  Value: {2}  =====\n",
                                  DateTime.Now.TimeOfDay, TaskID, i);
                i++;
                try
                {
                    _StrBlder.Append(log);
                }
                finally
                {
                    m_IsWork2Start = true;
                }
            }
        }

        private static void Work2(int TaskID)
        {
            int i = 0;
            string log = "";

            System.Threading.SpinWait.SpinUntil(() => m_IsWork2Start);

            while ((i < RUN_LOOP) && (m_IsWork2Start))
            {
                log = String.Format("Time: {0}  Task : #{1}  Value: {2}  *****\n",
                                  DateTime.Now.TimeOfDay, TaskID, i);
                i++;
                try
                {
                    _StrBlder.Append(log);
                }
                finally
                {
                    m_IsWork3Start = true;
                }
            }
        }

        private static void Work3(int TaskID)
        {
            int i = 0;
            string log = "";

            System.Threading.SpinWait.SpinUntil(() => m_IsWork3Start);

            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0}  Task : #{1}  Value: {2}  ~~~~~\n",
                                  DateTime.Now.TimeOfDay, TaskID, i);
                i++;
                try
                {
                    _StrBlder.Append(log);
                }
                finally
                {
                }
            }
        }

        static void Main(string[] args)
        {
            _Tasks = new Task[_TaskNum];
            _StrBlder = new StringBuilder();

            _Tasks[0] = Task.Factory.StartNew((num) =>
            {
                var taskid = (int)num;
                Work1(taskid);
            }, 0);

            _Tasks[1] = Task.Factory.StartNew((num) =>
            {
                var taskid = (int)num;
                Work2(taskid);
            }, 1);

            _Tasks[2] = Task.Factory.StartNew((num) =>
            {
                var taskid = (int)num;
                Work3(taskid);
            }, 2);

            var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) =>
            {
                Task.WaitAll(_Tasks);
                Console.WriteLine("==========================================================");
                Console.WriteLine("All Phase is completed");
                Console.WriteLine("==========================================================");
                Console.WriteLine(_StrBlder);
            });

            try
            {
                finalTask.Wait();
            }
            catch (AggregateException aex)
            {
                Console.WriteLine("Task failed And Canceled" + aex.ToString());
            }
            finally
            {
            }
            Console.ReadLine();
        }
    }
}

测试结果:

Time: 08:41:33.5505335 Task : #0 Value: 0 =====

Time: 08:41:33.5725357 Task : #0 Value: 1 =====

Time: 08:41:33.5725357 Task : #0 Value: 2 =====

Time: 08:41:33.5725357 Task : #0 Value: 3 =====

Time: 08:41:33.5725357 Task : #0 Value: 4 =====

Time: 08:41:33.5725357 Task : #0 Value: 5 =====

Time: 08:41:33.5725357 Task : #0 Value: 6 =====

Time: 08:41:33.5725357 Task : #0 Value: 7 =====

Time: 08:41:33.5725357 Task : #0 Value: 8 =====

Time: 08:41:33.5725357 Task : #0 Value: 9 =====

Time: 08:41:33.5735358 Task : #1 Value: 0 *

Time: 08:41:33.5735358 Task : #1 Value: 1 *

Time: 08:41:33.5735358 Task : #1 Value: 2 *

Time: 08:41:33.5735358 Task : #1 Value: 3 *

Time: 08:41:33.5735358 Task : #1 Value: 4 *

Time: 08:41:33.5735358 Task : #1 Value: 5 *

Time: 08:41:33.5735358 Task : #1 Value: 6 *

Time: 08:41:33.5735358 Task : #1 Value: 7 *

Time: 08:41:33.5735358 Task : #1 Value: 8 *

Time: 08:41:33.5735358 Task : #1 Value: 9 *

Time: 08:41:33.5735358 Task : #2 Value: 0 ~

Time: 08:41:33.5735358 Task : #2 Value: 1 ~

Time: 08:41:33.5735358 Task : #2 Value: 2 ~

Time: 08:41:33.5735358 Task : #2 Value: 3 ~

Time: 08:41:33.5735358 Task : #2 Value: 4 ~

Time: 08:41:33.5735358 Task : #2 Value: 5 ~

Time: 08:41:33.5735358 Task : #2 Value: 6 ~

Time: 08:41:33.5735358 Task : #2 Value: 7 ~

Time: 08:41:33.5735358 Task : #2 Value: 8 ~

Time: 08:41:33.5735358 Task : #2 Value: 9 ~

操作 SpinWait 的另一种方式 – 实例化 SpinWait。

SpinWait 提供了两个方法和两个只读属性。

方法:

  • SpinWait.Reset() : 重置自旋计数器,将计数器置 0。效果就好像没调用过SpinOnce一样。
  • SpinWait.Once() : 执行一次自旋。当SpinWait自旋达到一定次数后,如果有必要当前线程会让出底层的时间片并触发上下文切换。

属性:

  • SpinWait.Count:方法执行单次自旋的次数。
  • SpinWait.NextSpinWillYield:一个bool值,表示下一次通过SpinOnce方法自旋是否会让出底层线程的时间片并发生上下文切换。
时间: 2024-11-01 13:52:21

C# 并行编程 之 自旋锁的使用的相关文章

多线程编程之自旋锁

一.什么是自旋锁 一直以为自旋锁也是用于多线程互斥的一种锁,原来不是! 自旋锁是专为防止多处理器并发(实现保护共享资源)而引入的一种锁机制.自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用.无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁.但是两者在调度机制上略有不同.对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态.但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁

并发编程--CAS自旋锁

在前两篇博客中我们介绍了并发编程--volatile应用与原理和并发编程--synchronized的实现原理(二),接下来我们介绍一下CAS自旋锁相关的知识. 一.自旋锁提出的背景 由于在多处理器系统环境中有些资源因为其有限性,有时需要互斥访问(mutual exclusion),这时会引入锁的机制,只有获取了锁的进程才能获取资源访问.即是每次只能有且只有一个进程能获取锁,才能进入自己的临界区,同一时间不能两个或两个以上进程进入临界区,当退出临界区时释放锁.设计互斥算法时总是会面临一种情况,即

高效编程之互斥锁和自旋锁的一些知识

两种锁的加锁原理 互斥锁:线程会从sleep(加锁)-->running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销. 自旋锁:线程一直是running(加锁-->解锁),死循环检测锁的标志位,机制不复杂. 两种锁的区别 互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长. 两种锁的应用 互斥锁用于临界区持锁时间比较长的

一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁

前续 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(一)——地:起因 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁 平行时空 在复制好上面那一行我就先停下来了,算是先占了个位置,虽然我知道大概要怎么写,不过感觉还是很乱. 我突然想到,既然那么纠结,那么混乱,那么不知所措,我们不如换个视角.记得高中时看过的为数不多的长篇小说<穆斯林的葬礼>,作者是:霍达(女),故事描写了两个发生在不同时代.有着不同的内容却又交错扭结的爱情悲剧,一个是“玉”的故事,一个是“月”

多线程中的锁系统(四)-谈谈自旋锁

目录 一:基础 二:自旋锁示例 三:SpinLock 四:继续SpinLock 五:总结 一:基础 内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式.用户模式构造和内核模式构造 优点:cpu利用最大化.它发现资源被锁住,请求就排队等候.线程切换到别处干活,直到接受到可用信号,线程再切回来继续处理请求. 缺点:托管代码->用户模式代码->内核代码损耗.线程上下文切换损耗. 在锁的时间比较短时,系统频繁忙于休眠.切换,是个很大的性能损耗. 自旋锁:原子操作+自循环.通常说的用户构造模式.

深入分析Linux自旋锁

原创 2016-08-12 tekkamanninja CU技术社区 作者| tekkamanninja本文版权由tekkamanninja所有,如需转载,请联系本公众号获取授权!在复习休眠的过程中,我想验证自旋锁中不可休眠,所以编写了一个在自旋锁中休眠的模块.但是在我的ARMv7的单核CPU(TI的A8芯片)中测试的时候,不会锁死,并且自旋锁可以多次获取.实验现象和我对自旋锁和休眠的理解有出路.      我后来我将这个模块放到自己的PC上测试,成功锁死了,说明我的模块原理上没有问题.但是为什

【读书笔记】.Net并行编程(三)---并行集合

为了让共享的数组,集合能够被多线程更新,我们现在(.net4.0之后)可以使用并发集合来实现这个功能.而System.Collections和System.Collections.Generic命名空间中所提供的经典列表,集合和数组都不是线程安全的,如果要使用,还需要添加代码来同步. 先看一个例子,通过并行循环向一个List<string>集合添加元素.因为List不是线程安全的,所以必须对Add方法加锁来串行化. 任务开始: private static int NUM_AES_KEYS =

C# 并行编程 之 并发集合 (.Net Framework 4.0)(转)

转载地址:http://blog.csdn.net/wangzhiyu1980/article/details/45497907 此文为个人学习<C#并行编程高级教程>的笔记,总结并调试了一些文章中的代码示例. 在以后开发过程中可以加以运用. 对于并行任务,与其相关紧密的就是对一些共享资源,数据结构的并行访问.经常要做的就是对一些队列进行加锁-解锁,然后执行类似插入,删除等等互斥操作. .NetFramework 4.0 中提供了一些封装好的支持并行操作数据容器,可以减少并行编程的复杂程度.

C#并行编程 (Barrier,CountdownEvent,ManualResetEventSlim,SemaphoreSlim,SpinLock,SpinWait )

背景 有时候必须访问变量.实例.方法.属性或者结构体,而这些并没有准备好用于并发访问,或者有时候需要执行部分代码,而这些代码必须单独运行,这是不得不通过将任务分解的方式让它们独立运行. 当任务和线程要访问共享的数据和资源的时候,您必须添加显示的同步,或者使用原子操作或锁. 之前的.NET Framework提供了昂贵的锁机制以及遗留的多线程模型,新的数据结构允许细粒度的并发和并行化,并且降低一定必要的开销,这些数据结构称为轻量级同步原语. 这些数据结构在关键场合下能够提供更好的性能,因为它们能够