导航

C#多线程中lock

Posted on 2010-04-27 13:01  阳光有约  阅读(411)  评论(0编辑  收藏  举报
(2009-05-13 09:45:00)
标签:

it

分类:DOTNET(C#)学习
经常碰到同时需要对某个数据进行操作,或者对某个文件进行读写操作,对于这些操作我们以前往往不能很好的进行处理,自从C#语言中引入了lock这个关键字,以上问题就比较容易予以解决了,下面就是一段简单的代码。
public class AccessControl()
  {
  
private static object privateObjectLock = new object();
  
public static AccessResult()
   {
  
lock(privateObjectLock)
  {
  
//数据操作语句
  }
  }
  }

  在做邮箱接收网关的时候遇到了以下的需求,要求为每一个邮箱开启一个接收线程,从POP3服务器上收取,然后将邮件存放到统一的FTP服务器上,要求邮件按收接顺充从1开始顺充编号。

  我实现的方法为,为每个邮箱new出实例,然后分别赋给POP3邮箱地址,用户名,密码等参数。这里涉及到一个编号同步的问题,因为每个接收邮件的线程都是自己执行,所以取得编号并且递增这个动作是互斥的。

  以一个静态变量表示编号如下

  class EmailInfo
  {
  
public static int CurrentNumber;
  }

  那在当前线程取得这个步骤为

_CurrentNumber=++EmailInfo.CurrentNumber;

  虽然此为一句,但在计算机运行时却分为多步,如下

  EmialInfo.CurrentNumber加1--EmailInfo.CurrentNumber返回值给_CurrentNumber,也许线程1执行了EmailInfo.CurrentNumber加1 的操作,但还没有取得返回值,此时线程2又执行了EmailInfo.CurrentNumber加1的操作,然后又线程1,线程2取得了返回值就是一样的,这样就失去了按顺序递增的作用。

  此时查找了网上有关线程同步的方法,其实用lock语句锁住递增的那一段即可。而介绍的相关用法为

  lock(this)
  {
  _CurrentNumber
=++EmailInfo.CurrentNumber;
  }

  本以为这样就可以成功,谁知道还是无效,反复查找才发现没弄清楚lock的用法。因为网上所讲的资料,举的例子比较简单,是直接new 出一个对像,然后为对像的一个函数创建了多个线程运行,所以它的同步只要lock住this即它自己就行了。因为此时只有一个实例在运,而我是new 出了多个对像,lock住每个自己的实例所以当然无效。

  所以自然想了一个解决方法,就lock住相同的一个实例就行了。

  因为我每个邮件接收线程的参数都是不同的,所以还是new出几个实像,但lock的方法改为如下

  先为EmailInfo加一个静态变量

  class EmailInfo
  {
  
public static object syncRoot = new object();
  
public static int CurrentNumber;
  }

  然后lock改为

  lock(EmailInfo.syncRoot)
  {
  _CurrentNumber
=++EmailInfo.CurrentNumber;
  }

  即可实现想要的效果了。

==========================================================

旧事重提了,或许很多人会奇怪,为什么 C# 不允许lock一个struct ? 例如:

public void ProcessTask(int taskid){
    lock(taskid){  .....   }
}

编译说lock只能使用引用类型。有些人聪明(我想我以前也有这样的"聪明"。。),这样做: lock((object)taskid){...}

但是,实际的经验告诉我,这样行不通,lock需要的是引用,严格来说是需要对象的实例。

即使对象在意义上是相同的,但是如果不是ReferenceEquals的话,那么将作为两个实例来对待,那么C# lock 的就不是同一个东西。也就是说,当你以为这个 lock 生效的话,它其实在做无用工。

在上面的例子,由于lock((object)taskid)每执行一次,taskid都进行一次装箱,而装箱后的对象不是同一个实例(都是完完全全的新的实例),所以 lock((object)taskid){...} 是白做了。

当然,lock((object)123){} 这样的做法也是一样有问题的。

但是这种就好点:lock(“helloworld“){} 。为什么只是“好点”,而不是“没有问题”了呢。原因在于DotNet分配字符串的机制。DotNet为每个Assembly里的字符串都分配固定的空间。所以每次引用“helloworld“的时候,是同一个实例。但是这个字符串不会在其他Assembly中得到共用。如果几个Assembly都是这样写的,那么它们各自有她们自己的“helloworld“

比较好的做法:

  • lock(this)...
  • lock(typeof(ThisType))
  • lock(GetType())//除非你明白这是干什么,否则不要乱来。
  • lock(SomeType.StaticSyncObject)
  • lock(someinst.SyncObject)

其他的一些不好的做法

  • lock(“task:“+id)
  • lock(filename)

当然,具体lock什么东西,是设计上的协议和规范。不过要注意的是,使用lock必须明确对象是不是想象中的同一实例。

如果需要针对一个变化的值,从它的意义上的Equals方面进行 lock ,那怎么办?