对某个共享代码区域(临界区)进行串行访问,使用lock来保证串行的安全。
lock (lockMe)
{
dict.Add(i.ToString(), DateTime.Now);
}
通过ILSpy反编译查看可以知道,lock是个语法糖,编译后其实是Monitor.Enter 和 Monitor.Exit 的封装。
try
{
Monitor.Enter(lockMe, ref lockTake);
dict.Add(i.ToString(), DateTime.Now);
}
finally
{
if (lockTake)
{
Monitor.Exit(lockMe);
}
}
首先,编译器要求lock中的所对象必须是引用类型。
其次,因为lock会用到对象头中的同步块索引来进行同步,值类型没有堆中的数据。
static 的作用域在AppDomain下都可见,此时在多线程环境中,通过static共享变量的方式来同步,不可避免会出现锁竞争。如果能将作用域范围缩小,比如缩小到Thread级别,就可以避免锁竞争。例如:ConcurrentBag就是一个好的例子。
ThreadStatic(Attribute):当前线程拿到的是定义好的值,其他线程拿到的可能是默认值(值类型可能是0,引用类型可能是null,需要注意容错)。
ThreadLocal:与ThreadStatic最大的区别在于ThreadStatic只在第一个线程初始化,ThreadLocal则会为每个线程初始化。
用来做数据库连接池:DB连接池 基于 ThreadLocal实现,每个线程只能看见自己的请求队列;
用来做链式追踪:比如Skywalking或Zipkin等,用到ThreadLocal做本地存储,记录完整的调用链条如:A -> B -> C -> D;
这种锁是基于Windows底层的内核数据结构来维护线程之间的同步,比如:
需要从用户态切换到内核态,相对来说比较重量级,相对耗费时间;内核模式的锁,不仅可用于创建线程同步,还可以创建进程同步。
例如下面的代码:
lock(obj)
{
... // todo [1ms]
}
大部分都是在临界区进行等待时间很短(比如1ms)的加锁,能不能让thread在CLR或C#层面内旋(自旋)一下,从而提高性能呢?使用用户态锁就可以避免上下文切换和内核切换带来的高开销。
保持线程在用户态又要尽可能少的消耗CPU时间
SpinLock在用法上和lock关键字差不多的。
class Program
{
public static SpinLock spinLock = new SpinLock();
public static int counter = 0;
static void Main(string[] args)
{
Parallel.For(1, 1000001, (i) =>
{
var lockTaken = false;
spinLock.Enter(ref lockTaken);
++counter;
spinLock.Exit();
}
});
Console.WriteLine($"counter={counter}");
Console.ReadLine();
}
CPU直接操作的,主要用在一些简单类型上:
class Program
{
public static SpinLock spinLock = new SpinLock();
public static int counter = 0;
static void Main(string[] args)
{
Parallel.For(1, 1000001, (i) =>
{
Interlocked.Increment(ref counter, 1);
});
Console.WriteLine($"counter={counter}");
Console.ReadLine();
}
混合锁:用户态模式+内核态模式
它是如何实现的?
它是如何实现的?
这个锁的内核版是 ReaderWriterLock,不带Slim就代表是内核态的锁。
这个锁顾名思义是读写锁,意思是:读可以并行,但写只能串行。EnterWriteLock() 需要等待所有的reader或writer锁结束,才能开始
这个锁可以实现类似MapReduce的效果。
它是如何实现的?
基于ManualResetEvent事件做了底层封装。
.NET中都有哪些线程安全的集合类型?
ConcurrentBag 对应非线程安全类型:List
ConcurrentQueue 对应非线程安全类型:Queue
ConcurrentStack 对应非线程安全类型:Stack
ConcurrentDictionary 对应非线程安全类型:Dictionary
BlockingCollection 意为 阻塞集合。
线程安全的集合 可以转换为 阻塞集合,只要它实现了IProducerConsumerCollection接口BlockingCollection可以实现类似发布订阅的业务场景应用:
同样的代码,通过共享变量控制工作线程是否要结束自己,在Debug模式下没有问题,但是在Release模式下有问题。
JIT提供了错误的决策导致CPU在解析代码时做了优化,将 共享变量 存放在了CPU的寄存器中。
本篇,我们复习了锁机制相关的知识点。下一篇,我们将复习一下常见的.NET多线程相关的性能优化实践。
作者:周旭龙
出处:https://edisonchou.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。