理解C#的Lock语法意义

一. 为什么要lock,lock了什么?

当我们使用线程的时候,效率最高的方式当然是异步,即各个线程同时运行,其间不相互依赖和等待。但当不同的线程都需要访问某个资源的时候,就需要同步机制了,也就是说当对同一个资源进行读写的时候,我们要使该资源在同一时刻只能被一个线程操作,以确保每个操作都是有效即时的,也即保证其操作的原子性。lock是C#中最常用的同步方式,格式为lock(objectA){codeB} 。

lock(objectA){codeB} 看似简单,实际上有三个意思,这对于适当地使用它至关重要:
1. objectA被lock了吗?没有则由我来lock,否则一直等待,直至objectA被释放。
2. lock以后在执行codeB的期间其他线程不能调用codeB,也不能使用objectA。
3. 执行完codeB之后释放objectA,并且codeB可以被其他线程访问。

二. lock(this)怎么了?

我们看一个例子:

class C1
    {
        private bool deadlocked = true;//这个方法用到了lock,我们希望lock的代码在同一时刻只能由一个线程访问
        public void LockMe(object o)
        {
            lock (locker)
            {
                while (deadlocked)
                {
                    deadlocked = (bool)o;
                    Console.WriteLine("Foo: I am locked :(");
                    Thread.Sleep(500);
                }
            }
        }

        //所有线程都可以同时访问的方法
        public void DoNotLockMe()
        {
            Console.WriteLine("I am not locked :)");
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            C1 c1 = new C1();
            //在t1线程中调用LockMe,并将deadlock设为true(将出现死锁)
            Thread t1 = new Thread(c1.LockMe);
            t1.Start(true);
            Thread.Sleep(100);
            //在主线程中lock c1
            lock (c1)
            {
                //调用没有被lock的方法
                c1.DoNotLockMe();
                //调用被lock的方法,并试图将deadlock解除
                c1.LockMe(false);
            }
        }
    }

  程序运行结果如上图所示。在t1线程中,LockMe调用了lock(this), 也就是Main函数中的c1,这时候在主线程中调用lock(c1)时,必须要等待t1中的lock块执行完毕之后才能访问c1,即所有c1相关的操作都无法完成,于是我们看到连c1.DoNotLockMe()都没有执行。

  让我们把C1的代码稍作改动,mian()主函数里面的代码保持不变:

class C1
    {
        private bool deadlocked = true;
        private object locker = new object();
        //这个方法用到了lock,我们希望lock的代码在同一时刻只能由一个线程访问
        public void LockMe(object o)
        {
            lock (locker)
            {
                while (deadlocked)
                {
                    deadlocked = (bool)o;
                    Console.WriteLine("Foo: I am locked :(");
                    Thread.Sleep(500);
                }
            }
        }

        //所有线程都可以同时访问的方法
        public void DoNotLockMe()
        {
            Console.WriteLine("I am not locked :)");
        }
    }

  程序运行结果如上图所示。这次我们使用一个私有成员作为锁定变量(locker),在LockMe中仅仅锁定这个私有locker,而不是整个对象。这时候重新运行程序,可以看到虽然t1出现了死锁,DoNotLockMe()仍然可以由主线程访问;LockMe()依然不能访问,原因是其中锁定的locker还没有被t1释放。

关键点:
  1. lock(this)的缺点就是在一个线程(例如本例的t1)通过执行该类的某个使用"lock(this)"的方法(例如本例的LockMe())锁定某对象之后, 导致整个对象无法被其他线程(例如本例的主线程)访问 - 因为很多人在其他线程(例如本例的主线程)中使用该类的时候会使用类似lock(c1)的代码。
  2. 锁定的不仅仅是lock段里的代码,锁本身也是线程安全的。
  3. 我们应该使用不影响其他操作的私有对象作为locker。
  4. 在使用lock的时候,被lock的对象(locker)一定要是引用类型的,如果是值类型,将导致每次lock的时候都会将该对象装箱为一个新的引用对象(事实上如果使用值类型,C#编译器(3.5.30729.1)在编译时就会给出一个错误)。

C#的值类型包括:结构体(数值类型,bool型,用户定义的结构体),枚举,可空类型。C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。

因此,如果有以下定义:
class A
{
}
struct S
{
}
int i;
object o;
string str;
A a=new A();
S s=new S();
则有下面的语法正确或者错误说明,敬请注意。
lock(i){}//错误
lock(o){}//正确
lock(str){}//正确
lock(a){}//正确
lock(s){}//错误

  Lock语句根本使用的就是Monitor.Enter和Monitor.Exit,也就是说lock(this)时执行Monitor.Enter(this),大括号结束时执行Monitor.Exit(this).他的意义在于什么呢,对于任何一个对象来说,他在内存中的第一部分放置的是所有方法的地址,第二部分放着一个索引,他指向CLR中的SyncBlock Cache区域中的一个SyncBlock.什么意思呢?就是说,当你执行Monitor.Enter(Object)时,如果object的索引值为负数,就从SyncBlock Cache中选区一个SyncBlock,将其地址放在object的索引中。这样就完成了以object为标志的锁定,其他的线程想再次进行Monitor.Enter(object)操作,将获得object为正数的索引,然后就等待。直到索引变为负数,即线程使用Monitor.Exit(object)将索引变为负数。

system.threading.monitor.enter(x);
try
{
   ...
}
finally
{
   system.threading.monitor.exit(x);
}

  某些系统类提供专门用于锁定的成员。例如,Array 类型提供 SyncRoot。许多集合类型也提供 SyncRoot。而自定义类推荐用私有的只读静态对象,比如:

  private static readonly object obj = new object();

  为什么要设置成只读的呢?这时因为如果在lock代码段中改变obj的值,其它线程就畅通无阻了,因为互斥锁的对象变了,object.ReferenceEquals必然返回false。

其它参考连接:

http://www.jb51.net/article/57220.htm

http://www.2cto.com/kf/201110/108043.html

http://www.csharpwin.com/csharpspace/12362r6119.shtml

时间: 2024-12-21 13:04:30

理解C#的Lock语法意义的相关文章

C# lock 语法糖实现原理--《.NET Core 底层入门》之自旋锁,互斥锁,混合锁,读写锁

原文:C# lock 语法糖实现原理--<.NET Core 底层入门>之自旋锁,互斥锁,混合锁,读写锁 在多线程环境中,多个线程可能会同时访问同一个资源,为了避免访问发生冲突,可以根据访问的复杂程度采取不同的措施 原子操作适用于简单的单个操作,无锁算法适用于相对简单的一连串操作,而线程锁适用于复杂的一连串操作 原子操作 修改状态要么成功且状态改变,要么失败且状态不变,并且外部只能观察到修改前或者修改后的状态,修改中途的状态不能被观察到 .NET 中,System.Threading.Inte

理解析取范式及合取范式的意义

初学离散数学的同学一定会对析取范式与合取范式的意义有所困惑.首先单就它的使用环境来分析,析取范式有一点想电路里的或门,合取范式有一点像电路里的与门.而且它们的真值表是一模一样的.我们就会纳闷,怎么不叫"或取范式"."与取范式",这样不是更加的明了直观吗? 当然它这么叫肯定是有它的道理在的.要不然,离散数学就不像离散数学了,而是像电路离散数学了. 离散数学,作为多门学科的一个核心指导学科.它有很强概括作用和实际作用.很多你很费劲才能搞懂的问题,用了离散数学,你就会发现

[C#基础]说说lock到底锁谁?

写在前面 最近一个月一直在弄文件传输组件,其中用到多线程的技术,但有的地方确实需要只能有一个线程来操作,如何才能保证只有一个线程呢?首先想到的就是锁的概念,最近在我们项目组中听的最多的也是锁谁,如何锁?看到有同事使用lock(this),也有lock(private static object),那就有点困惑了,lock到底锁谁才是最合适的呢? lock 首先先上官方Msdn的说法 lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区. 如果其他线程尝试进入锁定的代码,

说说lock到底要锁谁?

波安搬... http://www.cnblogs.com/wolf-sun/p/4209521.html ----------------------------------------------------- 分割线 ------------------------------------------------------------------ 写在前面 最近一个月一直在弄文件传输组件,其中用到多线程的技术,但有的地方确实需要只能有一个线程来操作,如何才能保证只有一个线程呢?首先想到的

C#学习之关于lock

转自https://blog.csdn.net/ycigwy/article/details/70209863 首先先上官方Msdn的说法 lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区. 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放. lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit. ThreadInterruptedException 引发,如果 Interrupt 中断等待输入 lock 语句的线程

typedefine 语法

#define是预处理指令,typedef是在编译时处理的,编译预处理和编译时处理有什么不同. 编译预处理会在编译前处理好,比如#define N 7 则遇到N的地方都直接用7替换,然后在进行整个程序的编译,typedef则是在编译时遇到你所定义的东西,再回到typedef语句中寻找解释,继续编译下去的.(碰到一个,回去处理一个,不是一下子处理好) #define,const,typedef的区别 #define 并不是定义变量, 只是用来做文本替换 例如: #define   PI   3.1

谈谈javascript语法里一些难点问题(转)

谈谈javascript语法里一些难点问题(一) 1)    引子 前不久我建立的技术群里一位MM问了一个这样的问题,她贴出的代码如下所示: var a = 1; function hehe() { window.alert(a); var a = 2; window.alert(a); } hehe(); 执行结果如下所示: 第一个alert: 第二个alert: 这是一个令人诧异的结果,为什么第一个弹出框显示的是undefined,而不是1呢?这种疑惑的原理我描述如下: 一个页面里直接定义在

基本的语法

2 基本的语法 在学习Python其它知识之前,先理解几个基本语法概念,这样才可以理解它的代码是怎么样排列组合的,否则编辑出来的文本文件并不能叫做程序,因为Python的运行环境并不能对它进行解释执行,没有正确的结果输出. 2.1 大小写区分 在Python程序中,对于每一个变量的名称都是大小写区分的,比如My和my是两个不同的变量. 2.2 空格和制表符 在编写程序时,都需要对程序进行排版,否则看起来不清楚,不明白,不容易理解.但在Python里,排版不仅仅是为了好看,还当作一种语法来看待.这

理解与配置android studio中的gradle

使用gradle构建android应用时,你总是需要这样一个文件:build.gradle.你应该已经看过这个文件了,如果没有看过的话,你现在就可以看一下,它没有多少内容.它的简洁性得益于它提供了很多对设置和属性的默认值.gradle是基于groovy语言的,但就使用它构建普通的工程的话,是可以不去学groovy的,如果想深入的做一下自定义的构建插件,可以考虑学一下groovy,因为它是基于java的,所以你有java基础的话,学习不会很难. 这篇博客旨让任何一个人能看懂android stud