What is the safest (and shortest) way do lock read/write access to static members in a multithreaded environment in C#?
Is it possible to do the threadsafe locking & unlocking on class level (so I don't keep repeating lock/unlock code every time static member access is needed)?
Edit: Sample code would be great :)
Edit: Should I use the volatile keyword or Thread.MemoryBarrier() to avoid multiprocessor caching or is that unnecessary? According to Jon Skeet only those will make changes visible to other processors? (Asked this separately here).
Small Values
For small values (basically any field that can be declared volatile), you can do the following:
private static volatile int backingField;
public static int Field
{
get { return backingField; }
set { backingField = value; }
}
Large Values
With large values the assignment won't be atomic if the value is larger then 32-bits on a 32-bit machine or 64-bits on a 64-bit machine. See the ECMA 335 12.6.6 spec. So for reference types and most of the built-in value types the assignment is atomic, however if you have some large struct, like:
struct BigStruct
{
public long value1, valuea0a, valuea0b, valuea0c, valuea0d, valuea0e;
public long value2, valuea0f, valuea0g, valuea0h, valuea0i, valuea0j;
public long value3;
}
In this case you will need some kind of locking around the get accessor. You could use ReaderWriterLockSlim for this which I've demonstrated below. Joe Duffy has advice on using ReaderWriterLockSlim vs ReaderWriterLock:
private static BigStruct notSafeField;
private static readonly ReaderWriterLockSlim slimLock =
new ReaderWriterLockSlim();
public static BigStruct Safe
{
get
{
slimLock.EnterReadLock();
var returnValue = notSafeField;
slimLock.ExitReadLock();
return returnValue;
}
set
{
slimLock.EnterWriteLock();
notSafeField = value;
slimLock.ExitWriteLock();
}
}
Unsafe Get-Accessor Demonstration
Here's the code I used to show the lack of atomicity when not using a lock in the get-accessor:
private static readonly object mutexLock = new object();
private static BigStruct notSafeField;
public static BigStruct NotSafe
{
get
{
// this operation is not atomic and not safe
return notSafeField;
}
set
{
lock (mutexLock)
{
notSafeField = value;
}
}
}
public static void Main(string[] args)
{
var t = new Thread(() =>
{
while (true)
{
var current = NotSafe;
if (current.value2 != (current.value1 * 2)
|| current.value3 != (current.value1 * 5))
{
throw new Exception(String.Format("{0},{1},{2}", current.value1, current.value2, current.value3));
}
}
});
t.Start();
for(int i=0; i<50; ++i)
{
var w = new Thread((state) =>
{
while(true)
{
var index = (int) state;
var newvalue = new BigStruct();
newvalue.value1 = index;
newvalue.value2 = index * 2;
newvalue.value3 = index * 5;
NotSafe = newvalue;
}
});
w.Start(i);
}
Console.ReadLine();
}
The safest and shortest way is to create a private, static field of type Object that is only used for locking (think of it as a "pad-lock" object). Use this and only this field to lock on as this prevent other types from locking up your code when then lock on the same type that you do.
If you lock on the type itself there is risk that another type will also decide to lock on your type and this could create deadlocks.
Here is an example:
class Test
{
static readonly Object fooLock = new Object();
static String foo;
public static String Foo
{
get { return foo; }
set
{
lock (fooLock)
{
foo = value;
}
}
}
}
Notice that I have create a private, static field for locking foo - I use that field to lock the write operations on that field.
Although you could just use a single mutex to control all the access to the class (effectively serializing the access to the class) I suggest you study the static class, determine which members are being used where and how and the use one or several ReaderWriterLock (code examples in the MSDN documentation) which provides access to several readers but only one writer at the same time.
That way you'll have a fine grained multithreaded class which will only block for writing but will allow several readers at the same time and which will allow writing to one member while reading another unrelated member.
class LockExample {
static object lockObject = new object();
static int _backingField = 17;
public static void NeedsLocking() {
lock(lockObject) {
// threadsafe now
}
}
public static int ReadWritePropertyThatNeedsLocking {
get {
lock(lockObject) {
// threadsafe now
return _backingField;
}
}
set {
lock(lockObject) {
// threadsafe now
_backingField = value;
}
}
}
}
lock on an object specifically created for this purpose rather than on typeof(LockExample) to prevent deadlock situations where others have locked on LockExample's type object.
Is it possible to do the threadsafe locking & unlocking on class level (so I don't keep repeating lock/unlock code every time static member access is needed)?
Only lock where you need it, and do it inside the callee rather than requiring the caller to do the locking.
Several others have already explained how to use the lock keyword with a private lock object, so I will just add this:
Be aware that even if you lock inside each method in your type, calling more than one method in a sequence can not be considered atomic. For example if you're implementing a dictionary and your interface has a Contains method and an Add method, calling Contains followed by Add will not be atomic. Someone could modify the dictionary between the calls to Contains and Add - i.e. there's a race condition. To work around this you would have to change the interface and offer a method like AddIfNotPresent (or similar) which encapsulates both the checking and the modification as a single action.
Jared Par has an excellent blog post on the topic (be sure to read the comments as well).
You should lock/unlock on each static member access, within the static accessor, as needed.
Keep a private object to use for locking, and lock as required. This keeps the locking as fine-grained as possible, which is very important. It also keeps the locking internal to the static class members. If you locked at the class level, your callers would become responsible for the locking, which would hurt usability.
I thank you all and I'm glad to share this demo program, inspired by the above contributions, that run 3 modes (not safe, mutex, slim).
Note that setting "Silent = false" will result in no conflict at all between the threads. Use this "Silent = false" option to make all threads write in the Console.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Test
{
class Program
{
//------------------------------------------------------------------------------
// Configuration.
const bool Silent = true;
const int Nb_Reading_Threads = 8;
const int Nb_Writing_Threads = 8;
//------------------------------------------------------------------------------
// Structured data.
public class Data_Set
{
public const int Size = 20;
public long[] T;
public Data_Set(long t)
{
T = new long[Size];
for (int i = 0; i < Size; i++)
T[i] = t;
}
public Data_Set(Data_Set DS)
{
Set(DS);
}
public void Set(Data_Set DS)
{
T = new long[Size];
for (int i = 0; i < Size; i++)
T[i] = DS.T[i];
}
}
private static Data_Set Data_Sample = new Data_Set(9999);
//------------------------------------------------------------------------------
// SAFE process.
public enum Mode { Unsafe, Mutex, Slim };
public static Mode Lock_Mode = Mode.Unsafe;
private static readonly object Mutex_Lock = new object();
private static readonly ReaderWriterLockSlim Slim_Lock = new ReaderWriterLockSlim();
public static Data_Set Safe_Data
{
get
{
switch (Lock_Mode)
{
case Mode.Mutex:
lock (Mutex_Lock)
{
return new Data_Set(Data_Sample);
}
case Mode.Slim:
Slim_Lock.EnterReadLock();
Data_Set DS = new Data_Set(Data_Sample);
Slim_Lock.ExitReadLock();
return DS;
default:
return new Data_Set(Data_Sample);
}
}
set
{
switch (Lock_Mode)
{
case Mode.Mutex:
lock (Mutex_Lock)
{
Data_Sample.Set(value);
}
break;
case Mode.Slim:
Slim_Lock.EnterWriteLock();
Data_Sample.Set(value);
Slim_Lock.ExitWriteLock();
break;
default:
Data_Sample.Set(value);
break;
}
}
}
//------------------------------------------------------------------------------
// Main function.
static void Main(string[] args)
{
// Console.
const int Columns = 120;
const int Lines = (Silent ? 50 : 500);
Console.SetBufferSize(Columns, Lines);
Console.SetWindowSize(Columns, 40);
// Threads.
const int Nb_Threads = Nb_Reading_Threads + Nb_Writing_Threads;
const int Max = (Silent ? 50000 : (Columns * (Lines - 5 - (3 * Nb_Threads))) / Nb_Threads);
while (true)
{
// Console.
Console.Clear();
Console.WriteLine("");
switch (Lock_Mode)
{
case Mode.Mutex:
Console.WriteLine("---------- Mutex ----------");
break;
case Mode.Slim:
Console.WriteLine("---------- Slim ----------");
break;
default:
Console.WriteLine("---------- Unsafe ----------");
break;
}
Console.WriteLine("");
Console.WriteLine(Nb_Reading_Threads + " reading threads + " + Nb_Writing_Threads + " writing threads");
Console.WriteLine("");
// Flags to monitor all threads.
bool[] Completed = new bool[Nb_Threads];
for (int i = 0; i < Nb_Threads; i++)
Completed[i] = false;
// Threads that change the values.
for (int W = 0; W < Nb_Writing_Threads; W++)
{
var Writing_Thread = new Thread((state) =>
{
int t = (int)state;
int u = t % 10;
Data_Set DS = new Data_Set(t + 1);
try
{
for (int k = 0; k < Max; k++)
{
Safe_Data = DS;
if (!Silent) Console.Write(u);
}
}
catch (Exception ex)
{
Console.WriteLine("\r\n" + "Writing thread " + (t + 1) + " / " + ex.Message + "\r\n");
}
Completed[Nb_Reading_Threads + t] = true;
});
Writing_Thread.Start(W);
}
// Threads that read the values.
for (int R = 0; R < Nb_Reading_Threads; R++)
{
var Reading_Thread = new Thread((state) =>
{
int t = (int)state;
char u = (char)((int)('A') + (t % 10));
try
{
for (int j = 0; j < Max; j++)
{
Data_Set DS = Safe_Data;
for (int i = 0; i < Data_Set.Size; i++)
{
if (DS.T[i] != DS.T[0])
{
string Log = "";
for (int k = 0; k < Data_Set.Size; k++)
Log += DS.T[k] + " ";
throw new Exception("Iteration " + (i + 1) + "\r\n" + Log);
}
}
if (!Silent) Console.Write(u);
}
}
catch (Exception ex)
{
Console.WriteLine("\r\n" + "Reading thread " + (t + 1) + " / " + ex.Message + "\r\n");
}
Completed[t] = true;
});
Reading_Thread.Start(R);
}
// Wait for all threads to complete.
bool All_Completed = false;
while (!All_Completed)
{
All_Completed = true;
for (int i = 0; i < Nb_Threads; i++)
All_Completed &= Completed[i];
}
// END.
Console.WriteLine("");
Console.WriteLine("Done!");
Console.ReadLine();
// Toogle mode.
switch (Lock_Mode)
{
case Mode.Unsafe:
Lock_Mode = Mode.Mutex;
break;
case Mode.Mutex:
Lock_Mode = Mode.Slim;
break;
case Mode.Slim:
Lock_Mode = Mode.Unsafe;
break;
}
}
}
}
}
Locking in static methods sounds like a bad idea, for one thing if you use these static methods from class constructor you could run into some interesting side-effects due to loader locks (and the fact that class loaders can ignore other locks).
Related
I tried to implement Peterson Lock with C# like this
public class PetersonLock
{
private volatile bool[] flag = new bool[2];
private volatile int victim;
public int oneThreadId;
public void Lock() {
int i = Thread.CurrentThread.ManagedThreadId == oneThreadId ? 1 : 0;
int j = 1 - i;
flag[i] = true; /* A */
victim = i; /* B */
while (flag[j] && victim == i) { } /* C */
}
public void Unlock() {
int i = Thread.CurrentThread.ManagedThreadId == oneThreadId ? 1 : 0;
flag[i] = false;
}
}
But when I use this lock in 2 threads, it didn't work, someone said I should think about Instruction Reorder and use Memory Barrier. Do Line A, Line B and Line C reorder like A->B->C, or A->C->B, or C->A->B or other orders? So I changed my code to this:
public class PetersonLock
{
private volatile bool[] flag = new bool[2];
private volatile int victim;
public int oneThreadId;
public void Lock() {
int i = Thread.CurrentThread.ManagedThreadId == oneThreadId ? 1 : 0;
int j = 1 - i;
flag[i] = true;
Thread.MemoryBarrier(); // Is this line nesscessary?
victim = i;
Thread.MemoryBarrier(); // Is this line nesscessary?
while (flag[j] && victim == i) { }
}
public void Unlock() {
int i = Thread.CurrentThread.ManagedThreadId == oneThreadId ? 1 : 0;
flag[i] = false;
}
}
I don't know whether these two lines are both nesscessary?
Is there any rules to help me judge which line WILL be reordered and which
line WILL NOT be?
When should I use Memory Barrier?
According to Microsoft Documentation, there are simpler ways to synchronize code using locks and the Monitor class.
If you still want to stick to Thread.MemoryBarrier, I found this link quite interesting.
Regarding your code, I'm not very familiar with the PetersonLock, so don't take my words blindly. I would say that the first barrier is used to ensure that flag is set before the victim is set and the second barrier is needed to flush the memory to all cached memory lines so all the threads read the same value
I'm dealing with c# concurrent-queue and multi-threading in socket-programming tcp/ip
First, I've already done with socket-programming itself. That means, I've already finished coding about client, server and stuffs about communication itself
basic structure is pipe-lined(producer-consumer problem) and now I'm doing with bit conversion
below is brief summary about my code
client-socket ->server-socket -> concurrent_queue_1(with type byte[65536],Thread_1 process this) -> concurrent_queue_2(with type double[40,3500], Thread_2 process this) -> display-data or other work(It can be gpu-work)
*(double[40,3500] can be changed to other size)
Till now,I've implemented putting_data into queue1(Thread1) and just dequeuing all(Thread2) and, its speed is about 700Mbps
The reason I used two concurrent_queue is, I want communication,and type conversion work to be processed in background regardless of main procedure about control things.
Here is the code about my own concurrent_queue with Blocking
public class BlockingConcurrentQueue<T> : IDisposable
{
private readonly ConcurrentQueue<T> _internalQueue;
private AutoResetEvent _autoResetEvent;
private long _consumed;
private long _isAddingCompleted = 0;
private long _produced;
private long _sleeping;
public BlockingConcurrentQueue()
{
_internalQueue = new ConcurrentQueue<T>();
_produced = 0;
_consumed = 0;
_sleeping = 0;
_autoResetEvent = new AutoResetEvent(false);
}
public bool IsAddingCompleted
{
get
{
return Interlocked.Read(ref _isAddingCompleted) == 1;
}
}
public bool IsCompleted
{
get
{
if (Interlocked.Read(ref _isAddingCompleted) == 1 && _internalQueue.IsEmpty)
return true;
else
return false;
}
}
public void CompleteAdding()
{
Interlocked.Exchange(ref _isAddingCompleted, 1);
}
public void Dispose()
{
_autoResetEvent.Dispose();
}
public void Enqueue(T item)
{
_internalQueue.Enqueue(item);
if (Interlocked.Read(ref _isAddingCompleted) == 1)
throw new InvalidOperationException("Adding Completed.");
Interlocked.Increment(ref _produced);
if (Interlocked.Read(ref _sleeping) == 1)
{
Interlocked.Exchange(ref _sleeping, 0);
_autoResetEvent.Set();
}
}
public bool TryDequeue(out T result)
{
if (Interlocked.Read(ref _consumed) == Interlocked.Read(ref _produced))
{
Interlocked.Exchange(ref _sleeping, 1);
_autoResetEvent.WaitOne();
}
if (_internalQueue.TryDequeue(out result))
{
Interlocked.Increment(ref _consumed);
return true;
}
return false;
}
}
My question is here
As I mentioned above, concurrent_queue1's type is byte[65536] and 65536 bytes = 8192 double data.
(40 * 3500=8192 * 17.08984375)
I want merge multiple 8192 double data into form of double[40,3500](size can be changed)and enqueue to concurrent_queue2 with Thread2
It's easy to do it with naive-approach(using many complex for loop) but it's slow cuz, It copys all the
data and expose to upper class or layer.
I'm searching method automatically enqueuing with matched size like foreach loop automatically iterates through 2D-array in row-major way, not yet found
Is there any fast way to merge 1D-byte array into form of 2D-double array and enqueue it?
Thanks for your help!
I try to understand your conversion rule, so I write this conversion code. Use Parallel to speed up the calculation.
int maxSize = 65536;
byte[] dim1Array = new byte[maxSize];
for (int i = 0; i < maxSize; ++i)
{
dim1Array[i] = byte.Parse((i % 256).ToString());
}
int dim2Row = 40;
int dim2Column = 3500;
int byteToDoubleRatio = 8;
int toDoubleSize = maxSize / byteToDoubleRatio;
double[,] dim2Array = new double[dim2Row, dim2Column];
Parallel.For(0, toDoubleSize, i =>
{
int row = i / dim2Column;
int col = i % dim2Column;
int originByteIndex = row * dim2Column * byteToDoubleRatio + col * byteToDoubleRatio;
dim2Array[row, col] = BitConverter.ToDouble(
dim1Array,
originByteIndex);
});
Here's what I'm trying to do:
Get one html page from url which contains multiple links inside
Visit each link
Extract some data from visited link and create object using it
So far All i did is just simple and slow way:
public List<Link> searchLinks(string name)
{
List<Link> foundLinks = new List<Link>();
// getHtmlDocument() just returns HtmlDocument using input url.
HtmlDocument doc = getHtmlDocument(AU_SEARCH_URL + fixSpaces(name));
var link_list = doc.DocumentNode.SelectNodes(#"/html/body/div[#id='parent-container']/div[#id='main-content']/ol[#id='searchresult']/li/h2/a");
foreach (var link in link_list)
{
// TODO Threads
// getObject() creates object using data gathered
foundLinks.Add(getObject(link.InnerText, link.Attributes["href"].Value, getLatestEpisode(link.Attributes["href"].Value)));
}
return foundLinks;
}
To make it faster/efficient I need to implement threads, but I'm not sure how i should approach it, because I can't just randomly start threads, I need to wait for them to finish, thread.Join() kind of solves 'wait for threads to finish' problem, but it becomes not fast anymore i think, because threads will be launched after earlier one is finished.
The simplest way to offload the work to multiple threads would be to use Parallel.ForEach() in place of your current loop. Something like this:
Parallel.ForEach(link_list, link =>
{
foundLinks.Add(getObject(link.InnerText, link.Attributes["href"].Value, getLatestEpisode(link.Attributes["href"].Value)));
});
I'm not sure if there are other threading concerns in your overall code. (Note, for example, that this would no longer guarantee that the data would be added to foundLinks in the same order.) But as long as there's nothing explicitly preventing concurrent work from taking place then this would take advantage of threading over multiple CPU cores to process the work.
Maybe you should use Thread pool :
Example from MSDN :
using System;
using System.Threading;
public class Fibonacci
{
private int _n;
private int _fibOfN;
private ManualResetEvent _doneEvent;
public int N { get { return _n; } }
public int FibOfN { get { return _fibOfN; } }
// Constructor.
public Fibonacci(int n, ManualResetEvent doneEvent)
{
_n = n;
_doneEvent = doneEvent;
}
// Wrapper method for use with thread pool.
public void ThreadPoolCallback(Object threadContext)
{
int threadIndex = (int)threadContext;
Console.WriteLine("thread {0} started...", threadIndex);
_fibOfN = Calculate(_n);
Console.WriteLine("thread {0} result calculated...", threadIndex);
_doneEvent.Set();
}
// Recursive method that calculates the Nth Fibonacci number.
public int Calculate(int n)
{
if (n <= 1)
{
return n;
}
return Calculate(n - 1) + Calculate(n - 2);
}
}
public class ThreadPoolExample
{
static void Main()
{
const int FibonacciCalculations = 10;
// One event is used for each Fibonacci object.
ManualResetEvent[] doneEvents = new ManualResetEvent[FibonacciCalculations];
Fibonacci[] fibArray = new Fibonacci[FibonacciCalculations];
Random r = new Random();
// Configure and start threads using ThreadPool.
Console.WriteLine("launching {0} tasks...", FibonacciCalculations);
for (int i = 0; i < FibonacciCalculations; i++)
{
doneEvents[i] = new ManualResetEvent(false);
Fibonacci f = new Fibonacci(r.Next(20, 40), doneEvents[i]);
fibArray[i] = f;
ThreadPool.QueueUserWorkItem(f.ThreadPoolCallback, i);
}
// Wait for all threads in pool to calculate.
WaitHandle.WaitAll(doneEvents);
Console.WriteLine("All calculations are complete.");
// Display the results.
for (int i= 0; i<FibonacciCalculations; i++)
{
Fibonacci f = fibArray[i];
Console.WriteLine("Fibonacci({0}) = {1}", f.N, f.FibOfN);
}
}
}
For the following scenario, is there any difference regarding thread-safeness, result and performance between using MemoryBarrier
private SomeType field;
public SomeType Property
{
get
{
Thread.MemoryBarrier();
SomeType result = field;
Thread.MemoryBarrier();
return result;
}
set
{
Thread.MemoryBarrier();
field = value;
Thread.MemoryBarrier();
}
}
and lock statement (Monitor.Enter and Monitor.Exit)
private SomeType field;
private readonly object syncLock = new object();
public SomeType Property
{
get
{
lock (syncLock)
{
return field;
}
}
set
{
lock (syncLock)
{
field = value;
}
}
}
Because reference assignment is atomic so I think that in this scenarios we do need any locking mechanism.
Performance
The MemeoryBarrier is about 2x faster than lock implementation for Release. Here are my test results:
Lock
Normaly: 5397 ms
Passed as interface: 5431 ms
Double Barrier
Normaly: 2786 ms
Passed as interface: 3754 ms
volatile
Normaly: 250 ms
Passed as interface: 668 ms
Volatile Read/Write
Normaly: 253 ms
Passed as interface: 697 ms
ReaderWriterLockSlim
Normaly: 9272 ms
Passed as interface: 10040 ms
Single Barrier: freshness of Property
Normaly: 1491 ms
Passed as interface: 2510 ms
Single Barrier: other not reodering
Normaly: 1477 ms
Passed as interface: 2275 ms
Here is how I tested it in LINQPad (with optimization set in Preferences):
void Main()
{
"Lock".Dump();
string temp;
var a = new A();
var watch = Stopwatch.StartNew();
for (int i = 0; i < 100000000; ++i)
{
temp = a.Property;
a.Property = temp;
}
Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
Test(a);
"Double Barrier".Dump();
var b = new B();
watch.Restart();
for (int i = 0; i < 100000000; ++i)
{
temp = b.Property;
b.Property = temp;
}
Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
Test(b);
"volatile".Dump();
var c = new C();
watch.Restart();
for (int i = 0; i < 100000000; ++i)
{
temp = c.Property;
c.Property = temp;
}
Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
Test(c);
"Volatile Read/Write".Dump();
var d = new D();
watch.Restart();
for (int i = 0; i < 100000000; ++i)
{
temp = d.Property;
d.Property = temp;
}
Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
Test(d);
"ReaderWriterLockSlim".Dump();
var e = new E();
watch.Restart();
for (int i = 0; i < 100000000; ++i)
{
temp = e.Property;
e.Property = temp;
}
Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
Test(e);
"Single Barrier: freshness of Property".Dump();
var f = new F();
watch.Restart();
for (int i = 0; i < 100000000; ++i)
{
temp = f.Property;
f.Property = temp;
}
Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
Test(f);
"Single Barrier: other not reodering".Dump();
var g = new G();
watch.Restart();
for (int i = 0; i < 100000000; ++i)
{
temp = g.Property;
g.Property = temp;
}
Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
Test(g);
}
void Test(I a)
{
string temp;
var watch = Stopwatch.StartNew();
for (int i = 0; i < 100000000; ++i)
{
temp = a.Property;
a.Property = temp;
}
Console.WriteLine("Passed as interface: " + watch.ElapsedMilliseconds + " ms\n");
}
interface I
{
string Property { get; set; }
}
class A : I
{
private string field;
private readonly object syncLock = new object();
public string Property
{
get
{
lock (syncLock)
{
return field;
}
}
set
{
lock (syncLock)
{
field = value;
}
}
}
}
class B : I
{
private string field;
public string Property
{
get
{
Thread.MemoryBarrier();
string result = field;
Thread.MemoryBarrier();
return result;
}
set
{
Thread.MemoryBarrier();
field = value;
Thread.MemoryBarrier();
}
}
}
class C : I
{
private volatile string field;
public string Property
{
get
{
return field;
}
set
{
field = value;
}
}
}
class D : I
{
private string field;
public string Property
{
get
{
return Volatile.Read(ref field);
}
set
{
Volatile.Write(ref field, value);
}
}
}
class E : I
{
private string field;
private ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
public string Property
{
get
{
locker.EnterReadLock();
string result = field;
locker.ExitReadLock();
return result;
}
set
{
locker.EnterReadLock();
field = value;
locker.ExitReadLock();
}
}
}
class F : I
{
private string field;
public string Property
{
get
{
Thread.MemoryBarrier();
return field;
}
set
{
field = value;
Thread.MemoryBarrier();
}
}
}
class G : I
{
private string field;
public string Property
{
get
{
string result = field;
Thread.MemoryBarrier();
return result;
}
set
{
Thread.MemoryBarrier();
field = value;
}
}
}
is there any difference regarding thread-safeness?
Both ensure that appropriate barriers are set up around the read and write.
result?
In both cases two threads can race to write a value. However, reads and writes cannot move forwards or backwards in time past either the lock or the full fences.
performance?
You've written the code both ways. Now run it. If you want to know which is faster, run it and find out! If you have two horses and you want to know which is faster, race them. Don't ask strangers on the Internet which horse they think is faster.
That said, a better technique is set a performance goal, write the code to be clearly correct, and then test to see if you met your goal. If you did, don't waste your valuable time trying to optimize further code that is already fast enough; spend it optimizing something else that isn't fast enough.
A question you didn't ask:
What would you do?
I'd not write a multithreaded program, that's what I'd do. I'd use processes as my unit of concurrency if I had to.
If I had to write a multithreaded program then I would use the highest-level tool available. I'd use the Task Parallel Library, I'd use async-await, I'd use Lazy<T> and so on. I'd avoid shared memory; I'd treat threads as lightweight processes that returned a value asynchronously.
If I had to write a shared-memory multithreaded program then I would lock everything, all the time. We routinely write programs these days that fetch a billion bytes of video over a satellite link and send it to a phone. Twenty nanoseconds spent taking a lock isn't going to kill you.
I am not smart enough to try to write low-lock code, so I wouldn't do that at all. If I had to then I would use that low-lock code to build a higher-level abstraction and use that abstraction. Fortunately I don't have to because someone already has built the abstractions I need.
As long as the variable in question is one of the limited set of variables that can be fetched/set atomically (i.e. reference types), then yes, the two solutions are applying the same thread-related constraints.
That said, I would honestly expect the MemoryBarrier solution to perform worse than a lock. Accessing an uncontested lock block is very fast. It has been optimized specifically for that case. On the other hand, introducing a memory barrier, which affects not only the access to that one variable, as is the case for a lock, but all memory, could very easily have significant negative performance implications throughout other aspects of the application. You would of course need to do some testing to be sure, (of your real applications, because testing these two in isolation isn't going to reveal the fact that the memory barrier is forcing all of the rest of the application's memory to be synchronized, not just this one variable).
There is no difference as far as thread safety goes. However, I would prefer:
private SomeType field
public SomeType Property
{
get
{
return Volatile.Read(ref field);
}
set
{
Volatile.Write(ref field, value);
}
}
Or,
private volatile SomeType field
public SomeType Property
{
get
{
return field;
}
set
{
field = value;
}
}
In the following program, DummyMethod always print 5. But if we use the commented code instead, we get different values (i.e. 1, 2, 3, 4). Can anybody please explain why this is happenning?
delegate int Methodx(object obj);
static int DummyMethod(int i)
{
Console.WriteLine("In DummyMethod method i = " + i);
return i + 10;
}
static void Main(string[] args)
{
List<Methodx> methods = new List<Methodx>();
for (int i = 0; i < 5; ++i)
{
methods.Add(delegate(object obj) { return DummyMethod(i); });
}
//methods.Add(delegate(object obj) { return DummyMethod(1); });
//methods.Add(delegate(object obj) { return DummyMethod(2); });
//methods.Add(delegate(object obj) { return DummyMethod(3); });
//methods.Add(delegate(object obj) { return DummyMethod(4); });
foreach (var method in methods)
{
int c = method(null);
Console.WriteLine("In main method c = " + c);
}
}
Also if the following code is used, I get the desired result.
for (int i = 0; i < 5; ++i)
{
int j = i;
methods.Add(delegate(object obj) { return DummyMethod(j); });
}
The problem is that you're capturing the same variable i in every delegate - which by the end of the loop just has the value 5.
Instead, you want each delegate to capture a different variable, which means declaring a new variable in the loop:
for (int i = 0; i < 5; ++i)
{
int localCopy = i;
methods.Add(delegate(object obj) { return DummyMethod(localCopy); });
}
This is a pretty common "gotcha" - you can read a bit more about captured variables and closures in my closures article.
This article will probably help you understand what is happening (i.e. what a closure is): http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx
If you look at the code generated (using Reflector) you can see the difference:
private static void Method2()
{
List<Methodx> list = new List<Methodx>();
Methodx item = null;
<>c__DisplayClassa classa = new <>c__DisplayClassa();
classa.i = 0;
while (classa.i < 5)
{
if (item == null)
{
item = new Methodx(classa.<Method2>b__8);
}
list.Add(item);
classa.i++;
}
foreach (Methodx methodx2 in list)
{
Console.WriteLine("In main method c = " + methodx2(null));
}
}
When you use the initial code it creates a temporary class in the background, this class holds a reference to the "i" variable, so as per Jon's answer, you only see the final value of this.
private sealed class <>c__DisplayClassa
{
// Fields
public int i;
// Methods
public <>c__DisplayClassa();
public int <Method2>b__8(object obj);
}
I really recommend looking at the code in Reflector to see what's going on, its how I made sense of captured variables. Make sure you set the Optimization of the code to ".NET 1.0" in the Option menu, otherwise it'll hide all the behind scenes stuff.
I think it is because the variable i is put to the heap (it's a captured variable)
Take a look at this answer.