C#学习笔记之线程 - 同步上下文
同步上下文(Synchronization Contexts)
手动使用锁的一个替代方案是去声明锁。通过派生ContextBoundObject和应用Synchronization属性,你告诉CLR自动加锁。
using System; using System.Threading; using System.Runtime.Remoting.Contexts; [Synchronization] public class AutoLock : ContextBoundObject { public void Demo() { Console.Write ("Start..."); Thread.Sleep (1000); // We can't be preempted here Console.WriteLine ("end"); // thanks to automatic locking! } } public class Test { public static void Main() { AutoLock safeInstance = new AutoLock(); new Thread (safeInstance.Demo).Start(); // Call the Demo new Thread (safeInstance.Demo).Start(); // method 3 times safeInstance.Demo(); // concurrently. } } /// Output Start... end Start... end Start... end
CLR确保一次仅一个线程能够执行SafeInstance的代码。它是通过创建一个同步对象来实现的--并且围绕着每一个SafeInstance的方法和属性加锁。锁的范围--在这个例子中,是safeINstance对象--也称为同步上下文。
那么这是如何工作的呢?在Synchronization属性的命名空间中有一体线索:System.Runtime.Remoting.Contexts。
ContextBoundObject可以被认为是一个远程(Remote)对象,意味着所有方法的调用被拦截。为了使他可以被拦截,当我们实例化AutoLock时,CLR实际返回了一个代理--带有与AutoLock有相同方法和属性的对象,作为中间人。通过这个中间人自动加锁,总之,它围绕每一个函数调用增加了一个微妙。
自动同步不能被用于静态成员类型,也不能用于不是从ContextBoundObject中派生的类(如Window Form)。
在内部锁以同样的方式使用。你可以预期下面的例子与上面的有相同的结果:
using System; using System.Threading; using System.Runtime.Remoting.Contexts; [Synchronization] public class AutoLock : ContextBoundObject { public void Demo() { Console.Write ("Start..."); Thread.Sleep (1000); // We can't be preempted here Console.WriteLine ("end"); // thanks to automatic locking! }public void Test() { new Thread (Demo).Start(); // Call the Demo new Thread (Demo).Start(); // method 3 times new Thread (Demo).Start(); // method 3 times Console.ReadLine();} public static void Main(){new AutoLock().Test();} } /// Output Start... end Start... end Start... end
(注意我们加了Console.ReadLine()语句)。因为一次只能一个线程执行,因此3个新的线程将阻塞在Demo上直到Test返回--它要求ReadLine。因此,我们有相同的结果,但是必须在按下Enter之后。这个线程安全的锤子足够大以致能妨碍类里的其它非常有用的线程。
进一步,我们并没有解决前面提到的问题:如果AutoLock是一个集合类,我们仍然要求围绕下面的语句加锁,假设它允许在另外一个类上:if(safeInstance.Count>0)safeInstance.RemoeAt(0);除非这个类本身的代码是一个同步的ContextBoundObject!
一个同步对象能被扩展到单一对象的范围之外。默认,如果同步对象是从另外一个类的代码中实例化,那么2者有相同的上下文(也就是说,一个更大的锁)。这种行为可以通过指定Synchronization属性的一个整数标记来改变,下面就是标记的常量:
Constant | Meaning |
NOT_SUPPORTED | Equivalent to not using the Synchronized attribute |
SUPPORTED | Joins the existing synchronization context if instantiated from another synchronized object, otherwise remains unsynchronized. |
REQUIRED (DEFAULT) |
Joins the xisting synchronization context if instantiated from another synchronized object, otherwise creates a new context. |
REQUIRES_NEW | Always creates a new synchronization context. |
所以,如果SynchronizedA实例化一个SynchronizedB对象,那么他们有不同的同步上下文,如果它们象下面那样声明:
[Synchronization(SynchronizationAttribute.REQUIRES_NEW)]
public class SynchronizedB : ContextBoundObject{...}
同步上下文的范围越大,越容易去管理,但是用于并发的机会也越小。话又说回来,独立的同步上下文会引起死锁。如
[Synchronization] public class Deadlock : ContextBoundObject { public DeadLock Other; public void Demo() { Thread.Sleep (1000); Other.Hello(); } void Hello() { Console.WriteLine ("hello"); } } public class Test { static void Main() { Deadlock dead1 = new Deadlock(); Deadlock dead2 = new Deadlock(); dead1.Other = dead2; dead2.Other = dead1; new Thread (dead1.Demo).Start(); dead2.Demo(); } }
因为每一个DeadLock实例在Test内部创建--一个非同步类--每一个实例有它自己的同步上下文,因此,它拥有自己的锁。当2个对象彼此调用时,不用多久就会发生死锁(最多1秒钟)。如果DeadLock和Test是不同的团队写的,那么这个很难察觉。要负责Test类的人去意识到死锁是不切实际的,更不用说让他解决这个问题。与显式锁相比,死锁更加明显。
重入(Reentrancy)
一个线程安全的方法有时称为可再入,因为它可以被抢先执行,然后再次调用其它线程而不会有副作用。在通常情况下,线程安全和可再入的术语被认为是同义词或非常接近。
然而,重入在自动化加锁机制中有更多的内涵。如果Synchronization属性带有reentrant为true的参数被使用:[Synchronization[true]]
那么这个同步上下文的锁将被临时释放,当它离开上下文时。在前面的例子中,这将避免死锁的发生:这正是我们所要的。但是,这个期间的有一个副作用,任何线程可以自由在原始对象上调用任何方法(重入同步环境)并且释放
出了一开始想避免的并发的复杂性。这就是再入的问题。
因为[Synchronization(true)]应用在类级别,这个属性使得被类调用的每一个函数脱离上下文变成了Trojan。
当重入变得危险时,有时可以使用几个选项。如,假设在一个同步类内实现了多线程,通过委托这个逻辑到工作线程运行在一个独立的上下文中。这些工作线程可能是不合理的妨碍了彼此间或者与非再入性的原始对象之间的的通讯。
这突出了自动同步一个基本弱点:大范围的应用锁可能制造很多困难,但也可能不会出现困难。这些困难如死锁,重入及被阉割的并发性,使得在一些简单的场景中手动加锁比起它更加有优势。
转载于:https://www.cnblogs.com/C-Sharp2/p/4252777.html
C#学习笔记之线程 - 同步上下文相关推荐
- 【Linux学习笔记】线程同步 之 信号量 之 sem_t结构体
sem_t结构体: 里面封装了两个数据: 1.__align:long int 型,保存了信号量的值: 2.__size:char型,功能未知,常为空值,一般不需要使用; sem_t mutex;se ...
- VS2017 CUDA编程学习8:线程同步-原子操作
文章目录 前言 1. 原子操作的理解 2. C++ CUDA实现原子操作 3. 执行结果 总结 学习资料 VS2017 CUDA编程学习1:CUDA编程两变量加法运算 VS2017 CUDA编程学习2 ...
- java学习笔记15--多线程编程基础2
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...
- 多人网络游戏服务器开发基础学习笔记 II: 帧同步 | 游戏客户端预测原理分析 | FPS 游戏状态同步
这篇是对书本 网络多人游戏架构与编程 的学习第二篇(第一篇:多人网络游戏服务器开发基础学习笔记 I:基本知识 | 游戏设计模式 | 网游服务器层次结构 | 游戏对象序列化 | 游戏 RPC 框架 | ...
- java学习笔记14--多线程编程基础1
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为 ...
- 关于java线程同步的笔记_线程同步(JAVA笔记-线程基础篇)
在多线程应用程序中经常会遇到线程同步的问题.比如:两个线程A.线程B可能会 "同时" 执行同一段代码,或修改同一个变量.而很多时候我们是不希望这样的. 这时候,就需要用到线程同步. ...
- 简单理解线程同步上下文
为了线程安全,winform和wpf框架中规定只能使用UI线程操作控件,从其它线程上操作控件就会报跨线程异常.假如有这样一个场景:点击按纽,然后开始计算员工薪资,并将计算信息实时展示在一个文本框中,由 ...
- 5G NR - RACH学习笔记2 - 上行同步/Timing Advance
UE在获取下行同步(PSS/SSS)后, 需要通过RACH过程(随机接入过程)接入到基站(gNodeB),这个时候基站由于尚未与UE达到上行同步,因此首先需要执行上行同步. 那么怎么达到上行同步? - ...
- java笔记--关于线程同步(7种同步方式)
关于线程同步(7种方式) --如果朋友您想转载本文章请注明转载地址"http://www.cnblogs.com/XHJT/p/3897440.html"谢谢-- 为何要使用同步? ...
最新文章
- python HDF5 h5py 的用法
- 水仙花数(3.1)(Java)
- 虚拟内存——Windows核心编程学习手札之十四
- php里 like什么意思,php – 是LIKE,但是int表示应该检查的字符串
- Struts 验证框架实现步骤
- git 克隆新的 git 地址项目,用户名或者密码输入错误,再次执行不弹出用户名密码无法修改
- sql 除以_不可思议的SQL排名函数,被您无视了么?
- hive(II)--sql考查的高频问题
- android 设置folder类型,正确配置你的 Android 项目
- BBWebImage 设计思路
- ubuntu配置IP并且生效
- 不用爬虫也可以轻松获取 unsplash 图片
- 微信小程序开通直播的条件
- 飞腾平台编译文件系统
- 基于struts+spring+ibatis的轻量级J2EE开发
- 05全球IP归属地查询工具
- 微信小程序----开发rui-dtpicker多粒度日期组件
- hbase数据库连接池 未设置最大链接数导致数据库操作异常频发
- CG-34 浊度传感器 简单说明
- 服务器为什么会崩溃/宕机?