C# Interview questions

M

Mythran

Bruce Wood said:
Yeah, sorry. I re-read your post afterward. Need more coffee today, I
guess. :)

It's not your fault. It's the Canadians. They are trying to take over the
world by force, I tell you! They use subtle mind control technology that
only I know about! :p

lol

Mythran
 
G

Greg Young [MVP]

The most overused design pattern known to man.

It's the first pattern most learn and thus they use it everywhere so they
can feel like they are basing their design on patterns.

Greg
 
W

William Stacey [MVP]

Here is another Jim-Dandy that works. Notice after the instance is created,
the only overhead is a simple "if" test on future gets.

using System;
using System.Threading;

public sealed class Singleton
{
private static Singleton instance;

private Singleton() // Don't allow public constructor.
{
}

public static Singleton Instance
{
get
{
// Note simple and fast double-checked locking that actually
works. Also note instance does not
// need to be volatile. The Interlocked class does not wait, nor
does it transition to kernel mode and is only
// called if instance is null. If two or more thread happen to
get into the "if" before instance is set,
// then the CompareExchange statement takes care of that case as
well.
if (instance == null)
Interlocked.CompareExchange(ref instance, new Singleton(),
null);
return instance;
}
}
}

--
William Stacey [MVP]

| You can read about the Singleton design pattern here:
|
| http://www.yoda.arachsys.com/csharp/singleton.html
|
 
B

Bruce Wood

William said:
Here is another Jim-Dandy that works. Notice after the instance is created,
the only overhead is a simple "if" test on future gets.

using System;
using System.Threading;

public sealed class Singleton
{
private static Singleton instance;

private Singleton() // Don't allow public constructor.
{
}

public static Singleton Instance
{
get
{
// Note simple and fast double-checked locking that actually
works. Also note instance does not
// need to be volatile. The Interlocked class does not wait, nor
does it transition to kernel mode and is only
// called if instance is null. If two or more thread happen to
get into the "if" before instance is set,
// then the CompareExchange statement takes care of that case as
well.
if (instance == null)
Interlocked.CompareExchange(ref instance, new Singleton(),
null);
return instance;
}
}
}

Forgive my ignorance, but doesn't this just amount to double-check
locking? I'm no multithreading whiz, so I may be wrong, but I see
several problems with this code.

1) Testing for instance == null may suffer from caching problems: one
thread may have a cached value of "instance" that is still null, even
though it has been set on another thread. Given the next line, that
might not be a problem, but....

2) If the "if (instance == null)" line fails because of a caching
problem, or a race condition (instance was null when the test was done
but by the time we get to the CompareExchange call it is no longer
null), then "new Singleton()" will be called _before_ CompareExchange
is called, since the rule for method arguments is that they're
evaluated and their _values_ are passed to the method. So, you could
instantiate a second Singleton() and then, inside CompareExchange,
decide not to use it because some other thread has already set
"instance". Depending upon what the Singleton() constructor does, and
the consequences for having two Singleton instances around, this could
be bad... or not.

3) Even though CompareExchange guarantees that "instance" will tested
and set in one atomic operation, and I assume that it takes pains to
form a memory barrier so that other threads see the change as atomic,
I'm not sure that it guarantees that the _state of the Singleton
instance_ that was created preparing for the call will be correctly
updated into any memory cached by other threads. Are you sure that
CompareExchange creates a memory barrier in such a way that other
threads will not only see the correct "instance" reference, but will
also see the correct contents in all of the "instance"'s private fields?
 
W

William Stacey [MVP]

| Forgive my ignorance, but doesn't this just amount to double-check
| locking?

Exactly. But this method works. Actually, the old double-checked pattern
is ~supposed to work with the CLR memory model, but I would not feel good
about it.

| I'm no multithreading whiz, so I may be wrong, but I see
| several problems with this code.

All great questions Bruce.

| 1) Testing for instance == null may suffer from caching problems: one
| thread may have a cached value of "instance" that is still null, even
| though it has been set on another thread. Given the next line, that
| might not be a problem, but....

You actually answered this in 3. No thread will ever store a value of null
because the Get will always return an instance. And, as you said in 3,
Interlocked makes sure all cpu caches will work as expected. If this was
not the case, you could not really count on Interlocked at all.

|
| 2) If the "if (instance == null)" line fails because of a caching
| problem, or a race condition (instance was null when the test was done
| but by the time we get to the CompareExchange call it is no longer
| null), then "new Singleton()" will be called _before_ CompareExchange
| is called,

"new Singleton()" is only called if instance is null using atomic test. So
with two threads that get passed "if(instance == null)", only the first that
does the first Interlocked operation will create the instance. The other
thread will still run the Interlocked statement, but nothing will happen as
instance will not be null so it will just return and drop down and return
the instance in final line. Both threads will return the same instance.
There should never be a case where two singletons will be created per the
gaureentees that Interlocked gives us and the way this code is setup.

| 3) Even though CompareExchange guarantees that "instance" will tested
| and set in one atomic operation, and I assume that it takes pains to
| form a memory barrier so that other threads see the change as atomic,
| I'm not sure that it guarantees that the _state of the Singleton
| instance_ that was created preparing for the call will be correctly
| updated into any memory cached by other threads.

No other threads have a Singleton to cache yet - so cache is not an issue.
The first thread to win, creates it once. All other threads will see the
fully constructed object. We could refactor the code again to allow a
Clear() method, that would allow a "re-cycle" of the instance. Your
singleton would need to be able to handle multiple instances for some amount
of time however and it would complicate the code a bit more.

| Are you sure that CompareExchange creates a memory barrier in such a way
that other
| threads will not only see the correct "instance" reference, but will
| also see the correct contents in all of the "instance"'s private fields?

I have to rely on what MS says about this and they say it does. TMK, that
is one of the primary gaureentees of using CompareExchange this way.
Moreover, they also use this pattern inside the framework, so I hope it
works. I have not got it to fail yet, but I never say never (or almost
never if I can help it). Cheers.
 
L

Lucian Wischik

William Stacey said:
"new Singleton()" is only called if instance is null using atomic test. So
with two threads that get passed "if(instance == null)", only the first that
does the first Interlocked operation will create the instance. The other
thread will still run the Interlocked statement, but nothing will happen as
instance will not be null so it will just return and drop down and return
the instance in final line. Both threads will return the same instance.
There should never be a case where two singletons will be created per the
gaureentees that Interlocked gives us and the way this code is setup.

Are you sure? I don't understand why. As Bruce said, it looks to me
like TWO threads can get past the "if (instance==null)" test. And once
they're past it, it's guaranteed that they will construct an instance
of the Singleton object.

(True, as you say, only one of these newly created objects will be
stored in the pointer, and so both threads will return a pointer to
the same object. The second object just gets garbage-collected.)
 
W

William Stacey [MVP]

| Are you sure? I don't understand why. As Bruce said, it looks to me
| like TWO threads can get past the "if (instance==null)" test. And once
| they're past it, it's guaranteed that they will construct an instance
| of the Singleton object.

CompareExchange() is completely atomic. Only one thread at a time will ever
enter that statement, the first to enter will complete *fully before
returning (i.e. no thread switch will ever happen while inside an
Interlocked method) - this is a unique feature of the Interlocked members
which give them this special sauce. The second thread will "see" instance
!= null, so the test fails and it just returns doing nothing. So "new
Singleton()" can never be run twice. The only way "new Singleton()" would
ever run again is if "instance" was set to null again via some other code
or a weird memory failure or something.
 
L

Lucian Wischik

William Stacey said:
CompareExchange() is completely atomic. Only one thread at a time will ever
enter that statement, the first to enter will complete *fully before
returning (i.e. no thread switch will ever happen while inside an
Interlocked method) - this is a unique feature of the Interlocked members
which give them this special sauce. The second thread will "see" instance
!= null

*** SEQUENCE POINT 1 ****
if (instance!=null)
{
*** SEQUENCE POINT 2 *****
_temp = new Singleton();
*** SEQUENCE POINT 3 ****
CompareExchange(ref instance, _temp, null);
*** SEQUENCE POINT 4 ****
}
*** SEQUENCE POINT 5 ***

I've annotated your code with what I think are the sequence-points. I
also pulled out the argument-construction into a temporary variable,
because that seems most likely.

You say that "CompareExchange is completely atomic". True, and I
agree. This means that the span SP3--SP4 will be done atomically.

But the fact that SP3--SP4 is atomic does not prevent two threads from
both arriving at SP2. Nor does it stop them arriving at SP3. In other
words, the thread switch can occur at SP2 or at SP3.

Well, you seem to say that it does. This is what I don't understand.
You said that the second thread will "see" instance!=null. You seem to
be saying that the whole span SP1--SP4 is atomic, not just the span
SP3--SP4. Is this true? Why do you say so?
 
W

William Stacey [MVP]

You can't break the code out like that.

if (instance!=null)
{
//Point1
CompareExchange(ref instance, new Singleton(), null); //Point2
}
return instance;

1) Two thread can get to Point1. True.
2) Only 1 thread can enter Point2 at one time. The first to enter the
statement wins and will fully complete and return from that statement with
no thread switch. So instance can only be set once because of the atomic
test-and-set operation.
3) 2nd thread does the CompareExchange, but instance is not null, so it does
not do the "new Singleton()" part and returns.
4) Both threads return instance.
5) Future threads just return instance.

--
William Stacey [MVP]

| >CompareExchange() is completely atomic. Only one thread at a time will
ever
| >enter that statement, the first to enter will complete *fully before
| >returning (i.e. no thread switch will ever happen while inside an
| >Interlocked method) - this is a unique feature of the Interlocked members
| >which give them this special sauce. The second thread will "see"
instance
| >!= null
|
| *** SEQUENCE POINT 1 ****
| if (instance!=null)
| {
| *** SEQUENCE POINT 2 *****
| _temp = new Singleton();
| *** SEQUENCE POINT 3 ****
| CompareExchange(ref instance, _temp, null);
| *** SEQUENCE POINT 4 ****
| }
| *** SEQUENCE POINT 5 ***
|
| I've annotated your code with what I think are the sequence-points. I
| also pulled out the argument-construction into a temporary variable,
| because that seems most likely.
|
| You say that "CompareExchange is completely atomic". True, and I
| agree. This means that the span SP3--SP4 will be done atomically.
|
| But the fact that SP3--SP4 is atomic does not prevent two threads from
| both arriving at SP2. Nor does it stop them arriving at SP3. In other
| words, the thread switch can occur at SP2 or at SP3.
|
| Well, you seem to say that it does. This is what I don't understand.
| You said that the second thread will "see" instance!=null. You seem to
| be saying that the whole span SP1--SP4 is atomic, not just the span
| SP3--SP4. Is this true? Why do you say so?
|
| --
| Lucian
 
B

Bruce Wood

How very odd. That means that CompareExchange is not a method call,
then. It's something else?

If it's just a method like any other method, then according to the
documentation at

http://msdn2.microsoft.com/en-us/library/h7etff8w.aspx

"The compare and exchange operations are performed as an atomic
operation." Fair enough. That makes sense. However, the "new
Singleton()" occurs _before_ the call to CompareExchange. That
expression must be evaluated in order to make the call in the first
place. Once inside the method, CompareExchange makes certain guarantees
about the conditions under which the _instance variable will be set,
but I see no such guarantee about the evaluation of method arguments
before the method is invoked, nor do I understand how it would be
possible for a method to make those sorts of guarantees.

Then again, what I don't know about threading and locking would fill a
thick book. :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top