Best C # solution for multithreaded streaming read / write blocking?

What is the safest (and shortest) way to block read / write access to static members in a multithreaded C # environment?

Is it possible to lock and unlock thread safety at the class level (so I do not repeat the lock / unlock code every time I need access to a static member)?

Edit : The sample code will be great :)

Change Should I use the volatile or Thread.MemoryBarrier () keyword to avoid multiprocessing caching or is this not necessary? According to John Skeet, only those that will make the changes visible to other processors? (Set separately here ).

+4
source share
8 answers

The safest and shortest way is to create a private static field of type Object , which is used only for locking (think of it as a "pad-lock" object). Use this and only this field to lock, as this prevents your code from being locked by other types, and then locking the same type as you.

If you lock the type itself, there is a risk that another type will also decide to lock your type, and this may 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; } } } } 

Please note that I created a private static field to lock foo - I use this field to block write operations in this field.

+3
source

Small values

For small values ​​(basically any field that can be declared mutable), you can do the following:

 private static volatile int backingField; public static int Field { get { return backingField; } set { backingField = value; } } 

Big values

With large values, the assignment will not be atomic if the value is greater than 32-bit on a 32-bit machine or 64-bit on a 64-bit machine. See ECMA 335 12.6.6 . Therefore, for reference types and most built-in value types, the assignment is atomic, however if you have a large structure, for example:

 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 lock around the get accessor. You can use ReaderWriterLockSlim for this, as I demonstrated below. Joe Duffy tips for 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(); } } 

Insecure Get-Accessor Demo

Here is the code I used to show the lack of atomicity when not using lock in 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(); } 
+10
source

Although you can simply use one mutex to control all access to the class (effectively serializing access to the class), I suggest you study the static class, determine which members are used where and how, and also use one or more ReaderWriterLock (code examples in the MSDN documentation ), which provides access to several readers, but only to one author at a time.

Thus, you will have a fine-grained multithreaded class that will be blocked only for writing, but will allow multiple readers to read at the same time, which will allow one participant to write while reading another unrelated element.

+3
source
 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 for an object specially created for this purpose, and not for typeof(LockExample) to prevent deadlocks when others lock an object of type LockExample .

Is it possible to lock and unlock thread safety at the class level (so I do not repeat the lock / unlock code every time I need access to a static member)?

Only lock where you need it, and do it inside the called party, instead of requiring the caller to call lock ing.

+2
source

You must block / unblock each access to the static member, within the static accessor, as needed.

Keep a locked object to lock and lock as needed. This makes the fixation as fine as possible, which is very important. It also retains the lock inside the static members of the class. If you have blocked a class level, your subscribers will become responsible for the blocking, which will damage usability.

+1
source

Several others have already explained how to use the lock keyword with a locked lock object, so I'll just add this:

Remember that even if you block inside each method of its type, calling multiple methods in a sequence cannot be considered atomic. For example, if you implement a dictionary and your interface has a Contains method and an Add method, calling Contains and then adding it will not be atomic. Someone can change the dictionary between the calls β€œContains” and β€œAdd” - that is, the state of the race. To get around this, you will have to change the interface and propose a method such as AddIfNotPresent (or similar), which encapsulates both verification and modification as a single action.

Jared Par has an excellent blog post on the topic (be sure to also read the comments).

+1
source

I thank all of you, and I am pleased to share this demo program, inspired by the above contributions, which trigger 3 modes (unsafe, mutexes, subtle).

Note that setting "Silent = false" will result in a conflict between threads. Use this option "Silent = false" so that all streams are written to 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; } } } } } 
0
source

Blocking static methods sounds like a bad idea, on the one hand, if you use these static methods from a class constructor, you may encounter some interesting side effects due to loader locks (and the fact that class loaders can ignore other locks).

-2
source

All Articles