CLR via c# 笔记:高级同步对象

高级同步对象结合了用户态同步和内核态同步方式

一个简单的高级同步对象lock实现

在enter函数里,先调用interlock incresement方法,如果结果为1则表明其他线程没incresement,直接获取锁,如果补为1,则调用内核态锁

leave方法里,调用interlock decrement,如果为0,表明没有其他线程等待锁,否则,调用内核锁EVENT的SET

关于自旋,锁获取线程,重入

对于Mutex,要求获取锁的线程和释放锁的线程必须是同一个,这样锁对象必须记住获取锁的线程

重入的概念:一个线程获取到锁后,再次请求锁仍然能成功,但释放的次数必须和获取的次数相同

FCL的同步结构

Monitor

Monitor:支持自旋,锁的所有者线程,重入

每次new一个对象的时候,都会调用此对象的type对象的lock

Monitor能锁定任何一个对象,Monitor.Enter(obj),每一个对象上有一个sync对象索引(不是sync对象),这个sync对象用来锁定解锁obj,见下图

使用Monitor的一些问题

如果Monitor一个MarshalByRefObject 对象,实际是锁定了代理对象而非真实对象

如果传递一个Type对象给Monitor,将会锁定所有AppDomain里的这个类型

如果传递一个string给Monitor,将会锁定所有与此stirng值一样的变量,因为string是常量共用

如果传递一个值类型给Monitor,由于值类型永远是拷贝的,所以其实锁定的是新拷贝的对象

如果给某个函数加上sync attribute, 如果是静态函数,会lock住type obj,如果是非静态函数,则会lock this,lock this会有一定问题,比如如果在外层有lock这个obj,然后调用这个函数,函数由于等待this的锁,永远会等待下去。

每次调用对象的构造函数时,会锁定Type obj 所以不要在构造函数里写复杂的语句

lock

为了给开发者带来便利,减少忘记解锁的问题,c#团队定义了一个语法糖lock,lock会在锁定区域外部增加try catch finally 在finally解锁,这会有两个问题。1. 所有代码增加try catch会增加开销。2. 如果出现异常,仍然解锁,会使得其他依赖同步对象的线程拿到一个损坏的值继续run

读写锁

有名的Double check lock 技术

  public static Singleton GetSingleton() {        
// If the Singleton was already created, just return it (this is fast)       
if (s_value != null) return s_value; 
 
      Monitor.Enter(s_lock);  // Not created, let 1 thread create it       
if (s_value == null) {            // Still not created, create it          
    Singleton temp = new Singleton(); 
 
         // Save the reference in s_value (see discussion for details)    
    Volatile.Write(ref s_value, temp);        
}       
Monitor.Exit(s_lock);         // Return a reference to the one Singleton object        return s_value;     
} 

这里的volatile.write不能省略为直接给s_value赋值为 new Singleton() 因为new操作可能先分配内存 然后将地址赋值给s_value 然后调用构造函数,如果调用构造函数之前返回了,可能使得其他线程使用一个未构造的对象

比较直接的省事一点的方法是这样private static Singleton s_value = new Singleton();   

如果构造Sington开销不大 可以直接构造一个,然后用 Interlocked.CompareExchange(ref s_value, temp, null); 

Lazy<T>使用类似上面的技术来懒加载一个单例

EnsureInitialized<T>实现保证全局一次初始化

条件变量模式

可以做消费者生产者模式

异步同步

当一个线程为了等待某个资源而使得线程阻塞是一个很浪费资源且会引起某些性能风险的事情,如果很多线程都block了,则线程继续占用资源但不执行,线程池会试图创建更多的线程来使得CPU忙起来,当某一时刻大量线程unblock时,CPU会发生频繁的切换,使得性能急剧下降。异步同步为解决这个问题而生,异步同步发现线程需要block的时候,直接返回线程,public Task<Boolean> WaitAsync(Int32 millisecondsTimeout, CancellationToken cancellationToken); 这样线程可以转而取干别的事情

异步同步实现

在waitasync的时候,返回一个task对象  new TaskCompletionSource<Object>(); ,当别的线程使用完资源的时候,手动讲此task置为完成 tsk.SetResult(null); 所有的过程放在interlock里实现,没有真正的block发生

同步集合对象

ConcurrentQueue,ConcurrentStack,ConcurrentDictionary,ConcurrentBag,BlockCollection

同步集合对象有非阻塞版本(添加直接成功,获取如果是空则返回false),阻塞版本(添加需要检查是否超容量,超了就等待,获取如果是空集合则等待)

猜你喜欢

转载自blog.csdn.net/xuefeiliuyuxiu/article/details/81069973