简体   繁体   English

这是一个在C#中创建线程安全类的好设计吗?

[英]Is This a Good Design for Creating Thread-Safe Classes in C#?

Often, when I want a class which is thread-safe, I do something like the following: 通常,当我想要一个线程安全的类时,我会执行以下操作:

public class ThreadSafeClass
{
    private readonly object theLock = new object();

    private double propertyA;
    public double PropertyA
    {
        get
        {
            lock (theLock)
            {
                return propertyA;
            }
        }
        set
        {
            lock (theLock)
            {
                propertyA = value;
            }
        }
    }

    private double propertyB;
    public double PropertyB
    {
        get
        {
            lock (theLock)
            {
                return propertyB;
            }
        }
        set
        {
            lock (theLock)
            {
                propertyB = value;
            }
        }
    }

    public void SomeMethod()
    {
        lock (theLock)
        {
            PropertyA = 2.0 * PropertyB;
        }
    }
}

It works, but it is very verbose. 它有效,但它非常冗长。 Sometimes I even create a lock object for each method and property creating more verbosity and complexity. 有时我甚至为每个方法和属性创建一个锁定对象,从而产生更多的冗长和复杂性。

I know that it is also possible to lock classes using the Synchronization attribute but I'm not sure how well that scales -- as I often expect to have hundreds of thousands, if not millions, of instances of thread-safe objects. 我知道也可以使用Synchronization属性锁定类,但我不确定这些扩展程度如何 - 正如我经常期望的那样,有数十万甚至数百万个线程安全对象实例。 This approach would create a synchronization context for every instance of the class, and requires the class to be derived from ContextBoundObject and therefore could not be derived from anything else -- since C# doesn't allow for multiple inheritance -- which is a show stopper in many cases. 这种方法会为类的每个实例创建一个同步上下文,并且需要从ContextBoundObject派生类,因此不能从其他任何东西派生 - 因为C#不允许多重继承 - 这是一个show stopper在很多情况下。

Edit: As several of the responders have emphasized, there is no "silver bullet" thread-safe class design. 编辑:正如几位响应者所强调的那样,没有“银弹”线程安全的类设计。 I'm just trying to understand if the pattern I'm using is one of the good solutions. 我只是想了解我使用的模式是否是一个很好的解决方案。 Of course the best solution in any particular situation is problem dependent. 当然,在任何特定情况下,最佳解决方案都取决于问题。 Several of the answers below contain alternative designs which should be considered. 以下几个答案包含应考虑的替代设计。

Edit: Moreover, there is more than one definition of thread safety. 编辑:此外,线程安全有多个定义。 For example, in my implementation above, the following code would NOT be thread-safe: 例如,在我上面的实现中,以下代码不是线程安全的:

var myObject = new ThreadSafeClass();
myObject.PropertyA++; // NOT thread-safe

So, does the class definition above represent a good approach? 那么,上面的类定义是否代表了一种好的方法呢? If not, what would you recommend for a design with similar behavior which would be thread-safe for a similar set of uses? 如果没有,那么对于具有类似行为的设计,您会推荐什么?这种设计对于类似的用途是线程安全的?

There is no "one-size-fits-all" solution to the multi-threading problem. 多线程问题没有“一刀切”的解决方案。 Do some research on creating immutable classes and learn about the different synchronization primitives. 做一些关于创建不可变类的研究并了解不同的同步原语。

This is an example of a semi-immutable or the-programmers-immutable class . 这是一个半不可变程序员不可变类的例子。

public class ThreadSafeClass
{
    public double A { get; private set; }
    public double B { get; private set; }
    public double C { get; private set; }

    public ThreadSafeClass(double a, double b, double c)
    {
        A = a;
        B = b;
        C = c;
    }

    public ThreadSafeClass RecalculateA()
    {
        return new ThreadSafeClass(2.0 * B, B, C);
    }
}

This example moves your synchronization code into another class and serializes access to an instance. 此示例将同步代码移动到另一个类并序列化对实例的访问。 In reality, you don't really want more than one thread operating on an object at any given time. 实际上,在任何给定时间,您都不希望在对象上运行多个线程。

public class ThreadSafeClass
{
    public double PropertyA { get; set; }
    public double PropertyB { get; set; }
    public double PropertyC { get; set; }

    private ThreadSafeClass()
    {

    }

    public void ModifyClass()
    {
        // do stuff
    }

    public class Synchronizer
    {
        private ThreadSafeClass instance = new ThreadSafeClass();
        private readonly object locker = new object();

        public void Execute(Action<ThreadSafeClass> action)
        {
            lock (locker)
            {
                action(instance);
            }
        }

        public T Execute<T>(Func<ThreadSafeClass, T> func)
        {
            lock (locker)
            {
                return func(instance);
            }
        }
    }
}

Here is a quick example of how you would use it. 这是一个如何使用它的快速示例。 It may seem a little clunky but it allows you to execute many actions on the instance in one go. 它可能看起来有点笨重,但它允许您一次性对实例执行许多操作。

var syn = new ThreadSafeClass.Synchronizer();

syn.Execute(inst => { 
    inst.PropertyA = 2.0;
    inst.PropertyB = 2.0;
    inst.PropertyC = 2.0;
});

var a = syn.Execute<double>(inst => {
    return inst.PropertyA + inst.PropertyB;
});

I know this might sound like an smart a** answer but ... the BEST way to develop threadsafe classes is to actually know about multithreading, about its implications, its intricacies and what does it implies. 我知道这可能听起来像一个聪明的答案但是...开发线程安全类的最佳方法是实际了解多线程,它的含义,它的复杂性以及它意味着什么。 There's no silver bullet. 没有银弹。

Seriously... don't try to multithread (in production scenarios I mean) until you know what you're getting yourself into... It can be a huge mistake. 说真的......不要尝试多线程(在生产场景中我的意思),直到你知道自己在做什么......这可能是一个巨大的错误。

Edit: You should of course know the synchronization primitives of both the operating system and your language of choice (C# under Windows in this case, I guess). 编辑:您当然应该知道操作系统和您选择的语言的同步原语(在这种情况下,Windows下的C#,我猜)。

I'm sorry I'm not giving just the code to just make a class threadsafe. 对不起,我不是只给代码来创建一个类线程安全。 That's because it does not exist . 那是因为它不存在 A completely threadsafe class will probably just be slower than just avoiding threads and will probably act as a bottleneck to whatever you're doing... effectively undoing whatever you thing you're achieving by using threads. 一个完全线程安全的类可能只是比避免线程慢,并且可能会成为你正在做的任何事情的瓶颈......有效地撤消你通过使用线程实现的任何事情。

Bear in mind that the term "thread safe" is not specific; 请记住,“线程安全”一词并不具体; what you're doing here would be more accurately referred to as "synchronization" through the use of a Monitor lock. 通过使用Monitor锁定,您在这里所做的事情将被更准确地称为“同步”。

That said, the verbosity around synchronized code is pretty much unavoidable. 也就是说,围绕同步代码的详细程度几乎是不可避免的。 You could cut down on some of the whitespace in your example by turning things like this: 你可以通过这样的方式减少你的例子中的一些空格:

lock (theLock)
{
    propertyB = value;
}

into this: 进入这个:

lock (theLock) propertyB = value;

As to whether or not this is the right approach for you we really need more information. 至于这是否是正确的方法为你我们确实需要更多的信息。 Synchronization is just one approach to "thread safety"; 同步只是“线程安全”的一种方法; immutable objects, semaphores, etc. are all different mechanisms that fit different use-cases. 不可变对象,信号量等都是适合不同用例的不同机制。 For the simple example you provide (where it looks like you're trying to ensure the atomicity of a get or set operation), then it looks like you've done the right things, but if your code is intended to be more of an illustration than an example then things may not be as simple. 对于您提供的简单示例(看起来您正在尝试确保获取或设置操作的原子性),那么看起来您已经做了正确的事情,但是如果您的代码更多的是插图比一个例子然后事情可能不那么简单。

Since no else seems to be doing it, here is some analysis on your specific design. 由于似乎没有其他人这样做,这里有一些关于您的具体设计的分析。

  • Want to read any single property? 想要阅读任何单一房产? Threadsafe 线程
  • Want to update to any single property? 想要更新到任何一个属性? Threadsafe 线程
  • Want to read a single property and then update it based on its original value? 想要读取单个属性,然后根据其原始值更新它? Not Threadsafe 不是Threadsafe

Thread 2 could update the value between thread 1's read and update. 线程2可以更新线程1的读取和更新之间的值。

  • Want to update two related properties at the same time? 想要同时更新两个相关的属性吗? Not Threadsafe 不是Threadsafe

You could end up with Property A having thread 1's value and Property B having thread 2's value. 你可能最终得到属性A有线程1的值,属性B有线程2的值。

  1. Thread 1 Update A 线程1更新A.
  2. Thread 2 Update A 线程2更新A.
  3. Thread 1 Update B 线程1更新B.
  4. Thread 2 Update B 线程2更新B.

    • Want to read two related properties at the same time? 想同时阅读两个相关的属性吗? Not Threadsafe 不是Threadsafe

Again, you could be interrupted between the first and second read. 同样,您可能会在第一次和第二次读取之间中断。

I could continue, but you get the idea. 我可以继续,但你明白了。 Threadsafety is purely based on how you plan to access the objects and what promises you need to make. Threadsafety完全基于您计划访问对象的方式以及您需要做出的承诺。

You may find the Interlocked class helpful. 您可能会发现Interlocked类很有帮助。 It contains several atomic operations. 它包含几个原子操作。

One thing you could do that could help you avoid the extra code is use something like PostSharp to automatically inject those lock statements into your code, even if you had hundreds of them. 你可以做的一件事可以帮助你避免额外的代码使用类似PostSharp的东西来自动将这些lock语句注入你的代码,即使你有数百个。 All you'd need is one attribute attached to the class, and the attribute's implementation which would add the extra locking variables. 您需要的只是一个附加到类的属性,以及属性的实现,它将添加额外的锁定变量。

As per my comment above - it gets a little hairier if you want simultaneous readers allowed but only one writer allowed. 根据我上面的评论 - 如果你想要允许同时读者但只允许一位作家,那就会变得有点毛茸茸。 Note, if you have .NET 3.5, use ReaderWriterLockSlim rather than ReaderWriterLock for this type of pattern. 请注意,如果您使用的是.NET 3.5,请使用ReaderWriterLockSlim而不是ReaderWriterLock来处理此类型的模式。

public class ThreadSafeClass
{
    private readonly ReaderWriterLock theLock = new ReaderWriterLock();

    private double propertyA;
    public double PropertyA
    {
        get
        {
            theLock.AcquireReaderLock(Timeout.Infinite);
            try
            {
                return propertyA;
            }
            finally
            {
                theLock.ReleaseReaderLock();
            }
        }
        set
        {
            theLock.AcquireWriterLock(Timeout.Infinite);
            try
            {
                propertyA = value;
            }
            finally
            {
                theLock.ReleaseWriterLock();
            }
        }
    }

    private double propertyB;
    public double PropertyB
    {
        get
        {
            theLock.AcquireReaderLock(Timeout.Infinite);
            try
            {
                return propertyB;
            }
            finally
            {
                theLock.ReleaseReaderLock();
            }
        }
        set
        {
            theLock.AcquireWriterLock(Timeout.Infinite);
            try
            {
                propertyB = value;
            }
            finally
            {
                theLock.ReleaseWriterLock();
            }
        }
    }

    public void SomeMethod()
    {
        theLock.AcquireWriterLock(Timeout.Infinite);
        try
        {
            theLock.AcquireReaderLock(Timeout.Infinite);
            try
            {
                PropertyA = 2.0 * PropertyB;
            }
            finally
            {
                theLock.ReleaseReaderLock();
            }
        }
        finally
        {
            theLock.ReleaseWriterLock();
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM