Punit said:
I was under the impression that we need to synchronize access to all shared
variables between the threads because the examples i saw that generally books
giv for synchronization is generally a integer counter that is shared between
different threads. If all we have to worry about is mutable reference
variables and not value types, then why should someone worry about a integer
counter being accessed by multiple threads?
I am sorry if I'm not explaining this well. Let me try again...
The issue here is whether the variable is a) mutable, and b) that
mutation can be seen by multiple threads. Value types can be mutable as
well, but what I'm saying is that if you pass a copy of the value type,
then the original can mutate and it doesn't matter. Passing a copy of a
reference type variable just passes the reference, and so mutations in
the instance can still be seen by multiple threads.
Or did I still get the concept wrong??!! I am so confused... Why a string
value or an integer value does not need synchronized access by multiple
threads?
The string type is a reference type, but a string instance is immutable
and the string class is thread-safe. So, you can for all intents and
purposes treat it in the same way you'd treat a copy of a value type.
That is, you can pass the value of a string variable without any problem
to another thread, because even though it's a reference type, you are
guaranteed that the instance's properties won't actually change.
The same is not true of the string variable itself. That is, if you
have a string variable and it itself is being accessed by multiple
threads, then access to the _variable_ needs to be synchronized. But in
that case, the variable itself is mutable and that's why you need the
synchronization. Likewise a mutable value type variable (such as the
integer counter you see in the examples you're talking about); as long
as you pass the _value_ of that variable, you're fine. It's when the
variable itself can be accessed by multiple threads that the issue comes
up, because in that case the threads are all operating on the same value.
For example if one thread say mythread is making changes to the string
variable say str which is immutable, its basically creating another string
with the modified value and re-assigning the new reference to str.
So far, so good. You are correct: changes to the string require a new
instance of a string object, which is then assigned back to the original
variable "str".
now when
we call invoke and pass str as a parameter.... are we not passing the same
reference.
You are passing the value of the reference variable. But, and this is
very important, the "str" variable itself is not referenced by the
invoked delegate. Only the value it contains is.
So basically the delegate would have the reference to the same
string which was modified in mythread.
The delegate has a reference to the same string instance, yes. But not
the variable referencing the string.
So if the delegate concurrently
accesses the str value while mythread is acessing it, why wouldnt it be a
problem? Similar thing for the integer counter...?
Define "access". If by "access" you simply mean that the reference is
read, then the answer is that the string class is thread-safe, and so
using it from multiple threads is fine.
If by "access" you mean that the reference is written to, then the
answer is that the string class is immutable, and so this kind of access
cannot happen.
Now, if you change your example to be a mutable class, then yes...that's
potentially a problem. This is why I recommend not passing reference
values for mutable types to invoked delegates if it's not necessary.
Note that if you pass the value of an integer counter to an invoked
delegate, you are not passing the counter itself, but rather the value
contained in the counter at the moment you invoked the delegate. The
counter itself can change without affecting the delegate's observed
value, because the delegate has a copy of the value, not the original
counter.
In the examples I presume you are looking at, the counter itself is
accessed by multiple threads, and so synchronization needs to be done.
Maybe some examples will help (I hope...I especially hope they don't
confuse things more
)):
// inherit Control to make it clear that we can call BeginInvoke.
// Also, note that I'm using BeginInvoke, so that I can illustrate
// the thread issues with just two threads. The same issues could
// exist using Invoke, but only if some thread other than the one
// calling Invoke is modifying the values. This could in fact be
// the main thread on which the invoked delegates run, but it could be
// some other thread as well. It just depends on the design of the
// program.
class TwoThreaded : Control
{
// The "volatile" keyword ensures that both threads, should they
// access these variables at the same time, get the correct, current
// value. This doesn't address synchronization issues; it just
// ensures that if you have a synchronization bug, at least the
// values are correct given the written code and the order the
// statements execute.
volatile int _i;
volatile string _str;
// Assume the instance uses this function in a second thread
void Thread1()
{
_i = 5;
// A copy of _i is passed, the value of 5
BeginInvoke(Delegate1, new object[] { _i });
// Even if the above invocation isn't run yet, it
// still gets 5 when it does, even though we change
// the variable here
_i = 10;
_str = "string 1";
// The reference contained by _str is passed; this is
// a copy of the reference, but not a copy of the object
// itself. It is also not a reference to the variable _str.
BeginInvoke(Delegate2, new object[] { _str });
// As with the above change to _i, this change to _str does
// not affect the reference passed to Delegate2. It sets
// a new reference for _str, but the string object "string 1"
// is still referenced by the parameter passed to Delegate2.
_str = "string 2";
// Here no parameters are passed. Delegate3 has an explicit
// use of the instance member _i.
BeginInvoke(Delegate3);
// If this statement executes before Delegate3, then the
// delegate will see this new value of _i:
_i = 15;
// And just to make sure there's at least some potential
// for making things more confusing, here's an anonymous
// delegate:
BeginInvoke((MethodInvoker)delegate() { Text = _str; });
_str = "string 3";
// This is very much like Delegate3. The anonymous delegate
// contains the instance member _str, and so if _str changes
// after the call to Invoke but before the delegate actually
// executes, the more-recent value for _str will be seen by
// the delegate.
// Finally, an example that is potentially very confusing, but
// is important to understand if you are using anonymous
// delegates:
string strLocal = "string 4";
BeginInvoke((MethodInvoker)delegate() { Text = strLocal; });
strLocal = "string5";
// This is just like the example where _str is passed. With
// the anonymous delegate, if the variable is contained in the
// delegate and can also be referenced elsewhere, that's a
// synchronization issue.
}
void Delegate1(int i)
{
// the value of i is the copy passed in Invoke.
// This is a copy of _i, not _i itself, and so _i
// may change without affecting the value of i
}
void Delegate2(string str)
{
// the value of str is the reference passed in Invoke
// Likewise, this is a copy of _str's reference (not
// a copy of the object itself, just of the reference
// to the object). The value of _str can be changed,
// setting it to a new object instance, without
// affecting the value of str.
}
void Delegate3()
{
int i = _i;
// the value of i is whatever _i is at the time this
// code runs. This easily could be different from
// the value of _i at the time Invoke() was called
// Note that other than the synchronization issue, this is
// otherwise fine. That is, if you really do want to see
// what the most up-to-date value for _i is, it's okay to
// do that.
}
}