C# 设计线程安全类

C# 设计线程安全类,c#,multithreading,thread-safety,C#,Multithreading,Thread Safety,阅读MSDN文档时,它总是让您知道类是否是线程安全的。我的问题是如何设计线程安全的类?我不是说用锁定调用类,我是说我在为Microsoft create XXX class\object工作,我想说它是“线程安全的”,我需要做什么 若要声明该类是线程安全的,则假定该类中的内部数据结构不会因多个线程的并发访问而损坏。要做出这样的断言,您需要在类中的关键代码段周围引入锁定(Java中的同步),这可能会导致多个并发线程执行的代码段损坏。使类线程安全的最简单、最简单的方法是使其成为线程安全的。它的美妙之

阅读MSDN文档时,它总是让您知道类是否是线程安全的。我的问题是如何设计线程安全的类?我不是说用锁定调用类,我是说我在为Microsoft create XXX class\object工作,我想说它是“线程安全的”,我需要做什么

若要声明该类是线程安全的,则假定该类中的内部数据结构不会因多个线程的并发访问而损坏。要做出这样的断言,您需要在类中的关键代码段周围引入锁定(Java中的同步),这可能会导致多个并发线程执行的代码段损坏。

使类线程安全的最简单、最简单的方法是使其成为线程安全的。它的美妙之处在于,您不必再为锁定而烦恼

诀窍:使所有实例变量在C#中成为只读(Java中为final)

  • 不可变对象一旦在构造函数中创建和初始化,就不能更改
  • 不可变对象是线程安全的。句号
  • 这与只有常量的类不同
  • 对于系统的可变部分,仍然需要考虑和处理锁定/同步属性。这是首先编写不可变类的原因之一

也可以看到这一点。

非泛型
ICollection
类提供线程安全的属性。和。很遗憾,您无法设置IsSynchronized。你可以阅读更多关于他们的信息

在您的类中,您可以有一些类似于IsSynchronized和Syncroot的东西,单独公开公共方法/属性,并在方法体中检查它们。IsSynchronized将是一个只读属性,因此一旦初始化实例,您将无法修改它

bool synchronized = true;
var collection = new MyCustomCollection(synchronized);
var results = collection.DoSomething();

public class MyCustomCollection 
{
  public readonly bool IsSynchronized;
  public MyCustomCollection(bool synchronized)
  {
   IsSynchronized = synchronized
  }

  public ICollection DoSomething()
  { 
    //am wondering if there is a better way to do it without using if/else
    if(IsSynchronized)
    {
     lock(SyncRoot)
     {
       MyPrivateMethodToDoSomething();
     }
    }
    else
    {
      MyPrivateMethodToDoSomething();
    }

  }
}

您可以在

上阅读有关编写线程安全集合的更多信息。文档并不建议类是线程安全的,只有方法是线程安全的。为了断言一个方法是线程安全的,它必须能够在不给出错误结果的情况下从多个线程同时调用(错误结果可能是返回错误值的方法或对象进入无效状态)

当文件上说

任何公共静态文件(在Visual Studio中共享) 基本)此类型的成员是线程 安全的

这可能意味着类的静态成员不会改变共享状态

当文件上说

任何实例成员都不是 保证线程安全

这可能意味着方法具有最小的内部锁定

当文件上说

所有受保护的公众成员 此类是线程安全的,可能是 从多个数据库同时使用 线程


这可能意味着您可以调用的所有方法都在它们内部使用适当的锁定。这些方法也可能不会改变任何共享状态,或者它是一种无锁的数据结构,在设计上允许在没有任何锁的情况下并发使用。

线程安全类就是保护类中的数据(实例变量)。最常用的方法是使用关键字。最常见的新手错误是使用整个类的锁,而不是更细粒度的锁:

lock (this)
{
   //do somethnig
}
问题是,如果这个类做了一些重要的事情,它会给你带来巨大的性能打击。一般的规则是锁尽可能少,锁的时间尽可能短

您可以在此处阅读更多内容:


当您已经开始更深入地理解多线程时,您还可以看看ReaderWriterLoch和信号量。但我建议你只从锁关键字开始。

除了这里的其他优秀答案之外,还要考虑另一个角度。

如果公共API具有不能以线程安全方式使用的多步骤操作,那么类的内部数据结构是100%线程安全的是不够的

考虑一个list类,它的构建方式是,无论有多少线程在执行什么类型的操作,list的内部数据结构都将始终保持一致且正常

考虑以下代码:

if (list.Count > 0)
{
    var item = list[0];
}
这里的问题是,在读取
Count
属性和通过
[0]
索引器读取第一个元素之间,另一个线程可能已经清除了列表的内容

创建公共API时,通常会忘记这种线程安全性。在这里,唯一的解决方案是调用代码手动锁定每种访问类型上的某些内容,以防止代码崩溃

解决此问题的一种方法是列表类型的作者考虑典型的使用场景,并将适当的方法添加到类型:

public bool TryGetFirstElement(out T element)
那么你会:

T element;
if (list.TryGetFirstElement(out element))
{
    ....

据推测,
TryGetFirstElement
以线程安全的方式工作,并且不会在无法读取第一个元素值的同时返回
true

请参见示例。我不是在测试线程安全性,我是在问您需要考虑什么来设计它……我有两个原因不同意您的观点。1.我们处理的不变性有很多种。2.“final”是Java,你的意思是让它们成为常量?如果是,我不同意。使类常量中的所有字段都具有线程安全性与使它们具有线程安全性大不相同当您需要线程实际共享数据时(我认为这是基于问题的情况)。final的意思是确保它在构造函数之后有一个值,并且该值只在赋值后读取。有很多例子证明了我的观点,即标记字段
readonly
不足以保证线程安全。如果它们是对其他对象的引用,而这些对象是可操纵的呢