问题
I've been using this pattern to initialize static data in my classes. It looks thread safe to me, but I know how subtle threading problems can be. Here's the code:
public class MyClass // bad code, do not use
{
static string _myResource = "";
static volatile bool _init = false;
public MyClass()
{
if (_init == true) return;
lock (_myResource)
{
if (_init == true) return;
Thread.Sleep(3000); // some operation that takes a long time
_myResource = "Hello World";
_init = true;
}
}
public string MyResource { get { return _myResource; } }
}
Are there any holes here? Maybe there is a simpler way to do this.
UPDATE: Consensus seems to be that a static constructor is the way to go. I came up with the following version using a static constructor.
public class MyClass
{
static MyClass() // a static constructor
{
Thread.Sleep(3000); // some operation that takes a long time
_myResource = "Hello World";
}
static string _myResource = null;
public MyClass() { LocalString = "Act locally"; } // an instance constructor
// use but don't modify
public bool MyResourceReady { get { return _myResource != null; } }
public string LocalString { get; set; }
}
I hope this is better.
回答1:
You can use static constructors to intialize your static variables, which C# guarantees will only be called once within each AppDomain. Not sure if you considered them.
So you can read this: http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx (Static Constructors)
And this: Is the C# static constructor thread safe?
回答2:
Performing a lock()
on _myResource
and changing it inside lock()
statement seems like a bad idea.
Consider following workflow:
- thread 1 calls
MyClass()
. - execution stops before line
_init = true;
right after assigning_myResource
. - processor switches to thread 2.
- thread 2 calls
MyClass()
. Since_init
is stillfalse
and refrence_myResource
changed, it succesfully enterslock()
statement block. _init
is stillfalse
, so thread 2 reassigns_myResource
.
Workaround: create a static object
and lock on this object instead of initialized resource:
private static readonly object _resourceLock = new object();
/*...*/
lock(_resourceLock)
{
/*...*/
}
回答3:
Your class is not safe:
- You change the object you're locking on after you've locked on it.
- You have a property that gets the resource without locking it.
- You lock on a primitive type, which is generally not a good practice.
This should do it for you:
public class MyClass
{
static readonly object _sync = new object();
static string _myResource = "";
static volatile bool _init = false;
public MyClass()
{
if (_init == true) return;
lock (_sync)
{
if (_init == true) return;
Thread.Sleep(3000); // some operation that takes a long time
_myResource = "Hello World";
_init = true;
}
}
public string MyResource
{
get
{
MyClass ret; // Correct
lock(_sync)
{
ret = _myResource;
}
return ret;
}
}
}
Update:
Correct, the static resource should not be returned directly... I've corrected my example accordingly.
回答4:
Depending on your use case (i.e. if threads don't need to pass information to each other using this variable), marking the member variable as [ThreadStatic]
may be a solution.
See here.
回答5:
static string _myResource = "";
...
public MyClass()
{
...
lock (_myResource)
{
}
}
Due to string interning, you should not lock on a string literal. If you lock on a string literal and that string literal is used by multiple classes then you may be sharing that lock. This can potentially cause unexpected behavior.
来源:https://stackoverflow.com/questions/2971211/thread-safe-initialization-of-static-variables