.NET线程同步之SpinLock构造
来源:互联网 发布:星空卫视直播软件下载 编辑:程序博客网 时间:2024/06/03 13:21
接着上一篇博客讨论了.NET线程同步之Interlocked构造,本篇博客来讨论一下SpinLock构造。
Interlocked构造虽然很好用,但是它只是对一个变量或一个字段做一个原子操作,如果我们想对将一组操作封装为原子性操作,或者我们希望某段代码任何时候都不能在多个线程中同时运行,就可以使用SpinLock。
我想到一个应用场景。System.Collections.Generic.Stack<T>
类是线程不安全的。Stack类内部维护了一个链表,push方法会增加一个元素到链表,让头指针指向它,并修改链表长度,如果多个线程同时修改头指针,会造成元素丢失,如果多个线程同时更新链表长度,会造成更新丢失。pop方法会移动头指针以删除栈顶的元素并更新链表长度,如果多个线程同时操作的话也会有同样的问题。这就是它为什么线程不安全。我使用SpinLock实现了一个简单的支持并发的栈,咱们一起体会一下SpinLock的用法。
public class MyConcurrentStack<T>{ public int Length { get; set; } = 0; private MyConcurrentStackItem<T> Head { get; set; } = new MyConcurrentStackItem<T>(); private SpinLock spinLock = new SpinLock(); /// <summary> /// 入栈 /// </summary> /// <param name="value">Value</param> public void Push(T value) { bool lockTocken = false; try { //请求锁 spinLock.Enter(ref lockTocken); MyConcurrentStackItem<T> item = new MyConcurrentStackItem<T>(); item.Value = value; item.Next = Head.Next; Head.Next = item; Length++; } finally { if (lockTocken) { //释放锁 spinLock.Exit(); } } } public T Pop() { bool lockTocken = false; try { //请求锁 spinLock.Enter(ref lockTocken); if (Head.Next == null) { throw new InvalidOperationException("栈内没有元素,无法出栈"); } MyConcurrentStackItem<T> top = Head.Next; Head.Next = Head.Next.Next; Length--; return top.Value; } finally { if (lockTocken) { //释放锁 spinLock.Exit(); } } } /// <summary> /// 返回所有栈内元素 /// </summary> /// <returns>The list.</returns> public List<T> ToList() { List<T> itemList = new List<T>(); MyConcurrentStackItem<T> temp = Head.Next; while (temp != null) { itemList.Add(temp.Value); temp = temp.Next; } return itemList; } /// <summary> /// 内部类,封装一个栈元素 /// </summary> public class MyConcurrentStackItem<T> { public T Value { get; set; } public MyConcurrentStackItem<T> Next { get; set; } }}
这个栈内部维护了一个MyConcurrentStackItem<T>
类型的栈,每次入栈和出栈都使用SpinLock请求同步锁,成功地拿到锁之后,再进行一组操作(移动头指针、更细Length等),操作完成后释放同步锁,保证了这些操作在任何时间都不能并发执行。
下面的代码测试了MyConcurrentStack<T>
的并发效果。
public class Class1{ static MyConcurrentStack<string> myStack = new MyConcurrentStack<string>(); static void Main(string[] args) { Parallel.For(0, 100, i => Method1()); Console.WriteLine("the final length of stack is " + myStack.Length); List<string> strList = myStack.ToList(); Console.WriteLine("the real items' count is " + strList.Count); } static void Method1() { myStack.Push("hello"); myStack.Push("world"); string str = myStack.Pop(); //Console.WriteLine("pop value : {0}", str); }}
Method1()
方法入栈两个元素再出栈一个元素,通过Parallel.For
来启动100个任务来运行Method1()
方法,之后线程池会分配若干个线程来运行任务,在这种并发情况下,最终输出栈的Length
属性和栈内元素数,如果多次运行的结果都是这两个数量一致,则可基本证明入栈和出栈操作使线程安全的。
下面重点介绍一下SpinLock的用法和特点。
SpinLock.Enter()
会传递一个lockTocken
,这个lockTocken
在传递给Enter方法之前必须是false,Enter方法返回后,lockTocken
会变成true。lockTocken
是一个局部临时变量,每次调用Enter方法,一般都会用一个新的lockTocken。
如果多个线程同时调用同一个SpinLock实例的Enter方法的话,只有一个线程会成功地返回,其它的线程会不停地请求,原地打转,这也是它被叫做SpinLock(一般被翻译为回旋锁)的原因。Enter方法的内部使用了SpinWait结构来确保不停原地打转的线程不会阻碍那个拥有锁的线程的运行。当拥有锁的线程执行完相关的操作后,会调用Exit方法来释放锁,从而使原地打转的线程能够非常快地拿到锁。
SpinLock跟大家熟悉的lock关键字的执行方法非常不一样,SpinLock每次请求同步锁的效率非常高,但如果请求不到的话,会一直请求而浪费CPU时间,所以它适合那种并发程度不高、竞争性不强的场景。而lock关键字每次请求同步锁的代价比较大,但如果请求失败,当前线程会阻塞而让出宝贵的CPU时间,直到锁被释放以后线程被唤醒,它更适合并发程度高、竞争性很强的场景。
- .NET线程同步之SpinLock构造
- 线程同步之自旋锁(SpinLock)
- .NET线程同步之Volatile构造
- .NET线程同步之Interlocked构造
- linux同步之spinlock
- 2.6 内核 同步机制之spinlock
- [.Net线程处理系列]专题五:线程同步——事件构造
- AsyncTask 同步线程池构造
- 线程之线程同步
- .NET 线程同步(2)
- .NET 线程同步(2)
- .net 线程和同步
- .NET 线程同步方法
- .NET线程同步之Interlocked和ReadWrite锁
- linux中的自旋锁spinlock和信号量用于线程同步的区别
- 线程同步之信号量同步
- 线程同步之事件同步
- 线程同步之信号量同步
- PYTHON数据分析入门
- Educational Codeforces Round 22 C The Tag Game(树的深度)
- MyBatis一级缓存的简单剖析
- Android中如何使按钮的背景变得透明
- 如何从CSDN上转载文章
- .NET线程同步之SpinLock构造
- Date and Time in C++
- ReactRouter升级 v2 to v4
- centos7 mysql数据库安装和配置
- BZOJ 1131 Sta
- C++可变参数列表处理宏va_list、va_start、va_end的使用
- POJ 3017 Cut the Sequence 笔记
- 阿里云ESC搭建wampserver后外网访问不到问题
- Git的工作原理