注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

天涯倦客的博客

祝福你朋友永远快乐!

 
 
 

日志

 
 

C#深入学习笔记---Lock  

2011-01-25 17:26:17|  分类: C# |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

1.1    定义:

1.1.1   临界区(Critical Section

  临界区是一段在同一时候只被一个线程进入/执行的代码块。 

  

1.1.2   lock 关键字

将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。此语句的形式如下:

Object thisLock = new Object();

lock (thisLock)

{

    // Critical code section

}

lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

1.2    Lock用法实例

下例使用线程和 lock。只要 lock 语句存在,语句块就是临界区并且 balance 永远不会是负数。

C深入学习笔记---Lock - 海里的贝壳 - apple的博客代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestLock
{
    /// <summary>
    
/// 
    
/// </summary>
    public class Account
    {
        private static readonly Object thisLock = new Object();
        int balance;

        Random r = new Random();

        public Account(int initial)
        {
            balance = initial;
        }

        /// <summary>
        
/// 
        
/// </summary>
        
/// <param name="amount"></param>
        
/// <returns></returns>
        int Withdraw(int amount)
        {
            // This condition will never be true unless the lock statement
            // is commented out:
            if (balance < 0)
            {
                throw new Exception("Negative Balance");
            }

            // Comment out the next line to see the effect of leaving out 
            // the lock keyword:
            lock (thisLock)
            {
                if (balance >= amount)
                {
                    Console.WriteLine("----------------------------:" + System.Threading.Thread.CurrentThread.Name + "---------------");

                    Console.WriteLine("Balance before Withdrawal :  " + balance);
                    Console.WriteLine("Amount to Withdraw        : -" + amount);
                    balance = balance - amount;
                    Console.WriteLine("Balance after Withdrawal  :  " + balance);
                    return amount;
                }
                else
                {
                    return 0; // transaction rejected
                }
            }
        }

        public void DoTransactions()
        {
            for (int i = 0; i < 100; i++)
            {
                Withdraw(r.Next(1, 100));
            }
        }

    }
}

 

执行结果如下图所示:

C深入学习笔记---Lock - 海里的贝壳 - apple的博客

1.3    Lock的使用准则

lock 调用块开始位置的 Enter 和块结束位置的 Exit< XMLNAMESPACE PREFIX ="O" />

通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)lock (typeof (MyType)) lock ("myLock") 违反此准则:

·             如果实例可以被公共访问,将出现 lock (this) 问题。

·             如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。

·             由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现lock(“myLock”) 问题。

最佳做法是定义 private 对象来锁定, private static 对象变量来保护所有实例所共有的数据。

针对上述三点原则我们用具体的代码来加深理解

1.3.1   为什么不要Lock值类型

为什么不能lock值类型,比如lock(1)呢?让我们对上面的实例代码进行修改,lock(thisLock)改为Lock(1),编译器时会出现int不是Lock语句要求的引用对象

 

C深入学习笔记---Lock - 海里的贝壳 - apple的博客

 

 

lock本质上Monitor.EnterMonitor.Enter会使值类型装箱,每次lock的是装箱后的对象。lock其实是类似编译器的语法糖,因此编译器直接限制住不能lock值类型。

 

1.3.2  锁定lock((object)1)可以吗?

我们还是使用上面的实例代码来进行验证,lock(thisLock)改为Lock((object)1),这个时候编译倒是没什么问题了,但是运行结果中会出现balance < 0,最后抛出异常.如下图所示:

C深入学习笔记---Lock - 海里的贝壳 - apple的博客

 明代码块并没有锁.究其原因是因为Lock(对象)中,对象为引用类型,其是通过判断object.ReferenceEquals((object)1, (object)1)始终返回false(因为每次装箱后都是不同对象),也就是说每次都会判断成未申请互斥锁,这样在同一时间,别的线程照样能够访问里面的代码,达不到同步的效果。

 

1.3.3  为什么不要Lock(this)?

我们先来看如下一段代码:

 

C深入学习笔记---Lock - 海里的贝壳 - apple的博客代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace TestLock
{
    
public class TestLockThis
    {
        
private bool deadlocked = true;
        
private static readonly object lockobject = new object();

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

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

    }
}
        
static void Main(string[] args)
        {
            TestLockThis TestLockThis 
= new TestLockThis();

            
//在t1线程中调用LockMe,并将deadlock设为true(将出现死锁)
            Thread t1 = new Thread(TestLockThis.LockMethod);
            t1.Start(
true);
            Thread.Sleep(
100);

            
//在主线程中lock c1
            lock (TestLockThis)
            {
                
//调用没有被lock的方法
                TestLockThis.NotLockMethod();
                
//调用被lock的方法,并试图将deadlock解除
                TestLockThis.LockMethod(false);
            }

            Console.Read();
        }

 

执行结果为:

C深入学习笔记---Lock - 海里的贝壳 - apple的博客

 

在t1线程中,LockMethod调用了lock(this), 也就是Main函数中的c1,这时候在主线程中调用lock(TestLockThis)时,因为TestLockThis已经被锁定,所以必须要等待t1中的lock块执行完毕之后才能访问锁定的代码,即lock块中的所有操作都无法完成,于是我们看到连TestLockThis.NotLockMethod()都没有执行。

 

1.4    Lock为什么不要Lock(null对象)

以上面实例代码为例,将lock(thisLock)改为Lock(null),会抛出异常

C深入学习笔记---Lock - 海里的贝壳 - apple的博客

 

事实上,lock 关键字就是用Monitor 类来实现的。例如:

lock(x)
{
  DoSomething();
}

这等效于:

System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
  DoSomething();
}
finally
{
  System.Threading.Monitor.Exit(obj);
}

使用 lock 关键字通常比直接使用 Monitor 类更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放监视器。这是通过 finally 关键字来实现的,无论是否引发异常它都执行关联的代码块。

  这里微软已经说得很清楚了,Lock就是用Monitor实现的,两者都是C#中对临界区功能的实现。用ILDASM打开含有以下代码的exe 或者dll也可以证实这一点(我并没有自己证实):

lock (lockobject)
{
  int i = 5;
}

反编译后的的IL代码为:

C深入学习笔记---Lock - 海里的贝壳 - apple的博客代码
IL_0045:  call       void [mscorlib]System.Threading.Monitor::Enter(object
IL_004a:  nop 
.
try 

  IL_004b:  nop 
  IL_004c:  ldc.i4.
5 
  IL_004d:  stloc.
1 
  IL_004e:  nop 
  IL_004f:  leave.s    IL_0059 
}  
// end .try 
finally 

  IL_0051:  ldloc.
3 
  IL_0052:  call       
void [mscorlib]System.Threading.Monitor::Exit(object
  IL_0057:  nop 
  IL_0058:  endfinally 
}  
// end handler

而对于Monitor,发现它的静态方法Enter(object obj)有一个异常类型ArgumentNullException

执行lock(null对象 )处,抛出未处理的异常:System.ArgumentNullException: 值不能为空!

 

1.5    Lock 对象为什么推荐为只读静态对象

在代码段中修改锁定对象,会出现 blance<0的情况,并会抛出异常

private static readonly object obj = new object();

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

 

  评论这张
 
阅读(670)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017