简体   繁体   English

线程之间不共享静态实例

[英]static instance is not shared between threads

I am using a singleton class that suppose to have a single static instance as follows: 我正在使用一个单例类,该类应该具有一个静态实例,如下所示:

    private static ISingletonClass _instance = null;

    public static ISingletonClass GetInstance(string id = null)
    {
        if (_instance == null)
        {
            if (id != null)
            {
                _instance = new SingletonClass(id);
            }
            else
            {
                throw new NullReferenceException("id is missing!");
            }
        }

        if (id != null && _instance.Id != id)
        {
            _instance = new SingletonClass(id); // changing instance
        }

        return _instance;
    }

All other code in the class is not static (including the Id property). 该类中的所有其他代码都不是静态的(包括Id属性)。 Early in the run, when there is still a single, thread I initialize the singleton with some id, like so: 在运行的早期,当仍然有单个线程时,我使用一些id初始化单例,如下所示:

SingletonClass.GetInstance(<some_not_null_id>);

The _instance is set to not null (checked it). _instance设置为不为null(选中)。 Later I create a few threads to do some tasks, that need, among else, to read information from the SingletonClass (no writing). 稍后,我创建一些线程来执行某些任务,这些任务需要从SingletonClass中读取信息(无需编写)。 According to any documentation I found, and answers in StackOverflow, the same instance should be available to all threads (I didn't use [ThreadStatic] or any other alike mechanism). 根据我发现的任何文档以及StackOverflow中的答案,所有线程都应使用相同的实例(我没有使用[ThreadStatic]或任何其他类似的机制)。

However, When trying to GetInstance() with no parameters from inside the threads I get NullException (the _instance member is Null). 但是,当尝试从线程内部不带任何参数的GetInstance()时,我得到NullException(_instance成员为Null)。

I'm using .NET version 4.5, and working with VS2012. 我正在使用.NET 4.5版,并使用VS2012。

Any ideas? 有任何想法吗?

First, your assumption about the exception thrown is incorrect: 首先,您对引发异常的假设是不正确的:

NullException (the _instance member is Null). NullException(_instance成员为Null)。

The only NullReferenceException that would be thrown from your GetInstance() method is the one you throw yourself. GetInstance()方法中引发的唯一NullReferenceException是您自己引发的异常。 Assuming you have no code anywhere else that resets the _instance value to null , the only place that method dereferences the _instance value is at a statement that cannot be reached without ensuring that _instance is initialized to some non-null value. 假设您没有其他地方可以将_instance值重置为null ,则该方法取消引用_instance值的唯一位置是在未确保_instance初始化为某些非空值的情况下无法到达的语句。

As for the broader problem, IMHO the biggest issue is that you have a poorly-defined problem, and a broken implementation. 至于更广泛的问题,恕我直言,最大的问题是您定义的问题不明确,并且实现不完善。

Even ignoring the question of whether the "singleton" (it's not really a singleton, but for the sake of argument let's call it that for now) is ever changed, the initialization is not thread safe. 即使忽略“单身人士”(实际上不是单身人士,但为了争论起见,我们暂时称之为“单身人士”)是否曾经更改的问题,初始化也不是线程安全的。 You have the following potential race (assuming a single CPU core to make the illustration simple): 您具有以下潜在的竞争(假设使用单个CPU内核可以简化图示):

Thread 1                   Thread 2
--------                   --------
call GetInstance()
if (_instance == null)
--> preempted <--
                           call GetInstance()
                           if (_instance == null)
                           ...
                           _instance = new SingletonClass(id);
                           ...
                           return _instance;
                           --> preempted <--
if (_instance == null)
...
_instance = new SingletonClass(id);
...
return _instance;

As you can see in the above example, the way the code is written now, each thread could independently try to retrieve the instance, would see the current value as null , and would create a new instance object to be returned. 在上面的示例中可以看到,现在编写代码的方式,每个线程可以独立尝试检索实例,将当前值视为null ,并创建要返回的新实例对象。

For a true singleton, the best way to implement this is with the Lazy<T> class: 对于真正的单例,最好的实现方法是使用Lazy<T>类:

private static readonly Lazy<SingletonClass> _instance =
   new Lazy<SingletonClass>(() => new SingletonClass());

public static SingletonClass Instance { get { return _instance.Value; } }

In this case, the Lazy<T> class handles all of the initialization work, including ensuring it's done in a thread-safe way. 在这种情况下, Lazy<T>类将处理所有初始化工作,包括确保以线程安全的方式完成初始化工作。

In your case, where you do not have a true singleton, the above won't work. 在您的情况下,如果您没有真正的单身人士,则上述方法将无效。 The Lazy<T> pattern only works when something is to be initialized just once, but you want to be able to change it on the fly. Lazy<T>模式仅在某些内容仅被初始化一次时起作用,但是您希望能够即时对其进行更改。 Given that, you need something more like this: 鉴于此,您需要更多类似以下内容:

private static ISingletonClass _instance = null;
private static readonly object _lock = new object();

public static ISingletonClass GetInstance(string id = null)
{
    lock (_object)
    {
        if (_instance == null || (id != null && _instance.Id != id))
        {
            if (id == null)
            {
                throw new ArgumentNullException("id");
            }

            _instance = new SingletonClass(id);
        }

        return _instance;
    }
}

The above will ensure against threads initializing the field concurrently. 上面将确保防止线程同时初始化该字段。 They can race only to the lock, and then one thread is guaranteed to be the only one to initialize the object, assuming each thread passes the same value for id . 它们只能竞争锁,然后确保每个线程都传递唯一的id来初始化对象。

That said, that only fixes the basic thread-safety issue in your code. 就是说,那只能解决代码中的基本线程安全问题。 There are some bigger problems. 还有一些更大的问题。

First, what do you expect the code to do if and when one thread retrieves the current instance, then some other thread changes the current instance before the first thread is done using the instance it retrieved? 首先,如果一个线程检索了当前实例,然后其他线程在使用检索到的实例完成第一个线程之前更改了当前实例,那么您希望代码做什么? I'm not saying this is inherently broken, but it is at the very least very fragile, and you absolutely need to think about this scenario and decide for yourself what the right behavior should be in that case. 我并不是说这是天生的,但至少是非常脆弱的,您绝对需要考虑这种情况并自己确定在这种情况下应采取的正确行为。

Second, this is a really fragile mutation of the singleton pattern. 其次,这是单例模式的真正脆弱的突变。 A true singleton will have a single object, allocated only once during the lifetime of the process. 一个真正的单例将有一个对象,在该过程的生命周期内仅分配一次。 This ensures simplicity of design and predictability of behavior. 这样可以确保设计的简单性和行为的可预测性。

The implementation you have, will necessarily make it a lot harder to understand what the code is doing at any given point. 您拥有的实现必然会使在任何给定时间理解代码的工作变得更加困难。 It is practically guaranteed to add large amounts of time to some developer's day, either yours or someone else's, when some bug comes up and they are trying to track down the actual cause of the bug. 实际上,可以保证在出现一些错误并且他们试图找出错误的实际原因时,在您或其他人的某个开发人员的工作日上花费大量时间。

The fact that the instances of this class are tied to a string ID suggests that a better approach might be to maintain a Dictionary<string, SingletonClass> objects, requiring all callers to always specify the ID, and using that ID to retrieve (possibly initializing lazily of course) the object that thread needs at that moment. 此类的实例绑定到字符串ID的事实表明,更好的方法可能是维护Dictionary<string, SingletonClass>对象,要求所有调用者始终指定ID,并使用该ID进行检索(可能是初始化当然是懒惰的)当时线程需要的对象。

I strongly recommend a different design. 我强烈建议您使用其他设计。 But at a minimum, if you decide you must go this direction, make sure that you have considered all of the various combinations of thread events, and not only decided what the correct behavior is in each given scenario, but added code to ensure any assumed constraints. 但是至少,如果您决定必须朝这个方向发展,请确保已考虑了线程事件的所有各种组合,不仅确定了每种给定方案的正确行为,而且还添加了代码以确保所有假设约束。

I believe you want a true singleton with a value you can update. 我相信您想要一个具有可更新值的真正单身人士。 This updating needs to be threadsafe. 此更新必须是线程安全的。 The creation should be a separate method from the getting. 创建应该是与获取不同的方法。

private static readonly MyContainerClass _instance = new MyContainerClass(); //true singleton

private sealed class MyContainerClass //this is the singleton
{
   private ISingletonClass _value = null;

   public ISingletonClass Value //threadsafe access to your object
   {
      get { lock(this){ return _value; } }
   }

   public ISingletonClass CreateOrUpdateValue(string id) //threadsafe updating of your object
   {
      if (id==null) throw new ArgumentNullException("id is missing!");
      lock(this)
      {
        var instance = _instance.Value;

        if (instance == null || instance.Id != id)
          _instance.Value = new SingletonClass(id);

        return _instance.Value;
       }
    }
}

public static void CreateOrUpdateInstance(string id)
{
    _instance.CreateOrUpdateValue(id);
}

public static ISingletonClass GetInstance()
{
    var instance = _instance.Value;

    if (instance == null)
       throw new Exception("Instance has not been created");

    return _instance;
}

// this is like your original method if you really want it
public static ISingletonClass GetInstance(string id)
{
    return _instance.CreateOrUpdateValue(id);
}

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

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