Benutzer:MovGP0/Parallel/Locking

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
   MovGP0        Über mich        Hilfen        Artikel        Weblinks        Literatur        Zitate        Notizen        Programmierung        MSCert        Physik      


Locks block other threads. Try to avoid them.

Execution Speed:

  1. No Synchronization (immutable classes)
  2. Interlocked methods
  3. lock-Keyword or Monitor-class
  4. asynchronous locks
  5. something else
  • blocks access by other threads
  • never lock on public objects
    • other objects could try to lock the same object
  • never lock on complex objects that lock another object
    • could cause a deadlock
    • prefer a simple object instance
  • don't lock on MarshalByRefObject, string, or value types
    • MarshalByRefObject proxy object that protects the object
    • string are shared internally
    • value types would be boxed, thus preventing a lock
  • lock object needs additional memory
    • use other methods when requiring flyweight objects
False Correct
var _lock = new object();
var masterList = new List<long>();
const int NumTasks = 8;

public void Foo()
{
    var tasks = new Task[NumTasks];
    
    for(var i = 0; i < NumTasks; i++)
    {
        tasks[i] = Task.Factory.StartNew(() => 
        {
            
            for(var j = 0; j < 5000000; j++)
            {
                lock(_lock) // locking every time
                {
                    masterList.Add(j);
                }
            }
        });
    }
}
var _lock = new object();
var masterList = new List<long>();
const int NumTasks = 8;

public void Foo()
{
    var tasks = new Task[NumTasks];
    
    for(var i = 0; i < NumTasks; i++)
    {
        tasks[i] = Task.Factory.StartNew(() => 
        {
            var localList = new List<long>();
            for(var j = 0; j < 5000000; j++)
            {
                localList.Add(j);
            }
            lock(_lock) // locking only once
            {
                masterList.AddRange(localList);
            }
        });
    }
}

volatile-Keyword

[Bearbeiten | Quelltext bearbeiten]
  • Processor might reorder code for faster execution
  • x86/x64 processor reorder only certain instructions, while ARM reorders everything without special instruction
    • the CLR abstracts specifics, so usually there is no need to care about specifics
  • use of volatile, the Interlocked-methods, and lock prevents reordering of the code
False Correct
private bool isComplete = false;
private object _lock = new object();

private void Complete()
{
    if(!isComplete)
    {
        lock(_lock)
        {
            if(!isComplete)
            {
                DoCompleteionWork();
                isComplete = true;
            }
        }
    }
}
private volatile bool isComplete = false;
private object _lock = new object();

private void Complete()
{
    if(!isComplete)
    {
        lock(_lock)
        {
            if(!isComplete)
            {
                DoCompletetionWork();
                isComplete = true;
            }
        }
    }
}
  • Methods from the static Interlocked class are considered atomic
Important Interlocked-Methods
  • Add
  • CompareAndExchange
  • Increment
  • Decrement
  • Exchange
Examples
Increment CompareAndExchange
private int isComplete = 0;

private void Complete()
{
    if(Interlocked.Increment(ref isComplete) == 1)
    {
        DoCompletetionWork();
    }
}
enum State
{
    Executing, 
    Done
}

private int state = (int)State.Executing;

private void Complete()
{
    // might get called multiple times, but execute only once
    if(Interlocked.CompareAndExchange(ref state, (int)State.Done, (int)State.Executing) == (int)State.Executing)
    {
        DoCompletetionWork();
    }
}

Thread-save collections

[Bearbeiten | Quelltext bearbeiten]
  • repeating operation until executed correct
    • uses while(true) loop
  • slower than lock, wenn experiencing concurrent calls
  • hard to implement correct
  • hard to make it perfom better than locks
class LockFreeStack<T>
{
   private class Node
   {
      public T Value; 
      public Node Next;
   }
   
   private Node head;
   
   public void Push(T value)
   {
      var newNode = new Node
      {
         Value = value;
      }
      
      while(true)
      {
         newNode.Next = this.head;
         if(Interlocked.CompareExchange(ref head, newNode, newNode.Next) == newNode.Next)
         {
            return;
         }
      }
   }

   public void T Pop()
   {
      while(true)
      {
         var node = head;
         
         if(node == null)
         {
            return default(T);
         }
         
         if(Interlocked.CompareExchange(ref head, node.Next, node))
         {
            return node.Value;
         }
      }
   }
}
  • equivalent to lock keyword
  • optimal for short contention
  • retries for a given time to aquire the lock
    • gives up when a lock is not possible
Enter TryEnter
var _lock = new object();
bool taken = false;

private void Foo()
{
    try
    {
        // taken is set to true when lock is possible 
        Monitor.Enter(_lock, ref taken); 

        // do something
    }
    finally
    {
        if(taken)
        {
            Monitor.Exit(_lock);
        }
    }
}
var _lock = new object();
bool taken = false; 

private void Foo()
{
    try
    {
        Monitor.TryEnter(_lock, ref taken); 
        
        if(taken)
        {
           // do something
        }
        else
        {
           // do something else
        }
    }
    finally
    {
        if(taken)
        {
            Monitor.Exit(_lock);
        }
    }
}

Async Lock / Semaphore

[Bearbeiten | Quelltext bearbeiten]
  • requires .NET 4.5
  • avoids locking
  • requires the creation of a new task
Sync Async
class Program
{
   const int Size = 256;
   static int[] array = new int[Size];
   static int length = 0;
   static SemaphoreSlim semaphore = new SemaphoreSlim(1); // only one thread at a time

   static void Main()
   {
      var writerTask = Task.Factory.StartNew(WriterFunc);
      var readerTask = TaskFactory.StartNew(ReaderFunc);

      Console.WriteLine("Press any key to exit");
      Console.ReadKey();
   }

   static void WriterFunc()
   {
      while(true)
      {
         semaphore.Wait(); // wait for other threads to release semaphore 
         Console.WriteLine("Writer: obtain");

         for(int i = length; i < array.Length; i++)
         {
            array[i] = i * 2;
         }
         
         Console.WriteLine("Writer: release");
         semaphore.Release();
      }
   }

   static void ReaderFunc()
   {
      while(true)
      {
         semaphore.Wait(); // wait for other threads to release semaphore 
         Console.WriteLine("Reader: obtain");
         
         for(int i = length; i >= 0; i--)
         {
            array[i] = 0;
         }
         
         Console.WriteLine("Reader: release");
         semaphore.Release();
      }
   }
}
class Program
{
   const int Size = 256;
   static int[] array = new int[Size];
   static int length = 0;
   static SemaphoreSlim semaphore = new SemaphoreSlim(1); // only one thread at a time

   static void Main()
   {
      var writerTask = Task.Factory.StartNew(WriterFunc);
      var readerTask = TaskFactory.StartNew(ReaderFunc);

      Console.WriteLine("Press any key to exit");
      Console.ReadKey();
   }

   static void WriterFuncAsync()
   {
      semaphore.WaitAsync().ContinueWith(_ =>
      {
         Console.WriteLine("Writer: obtain");

         for(int i = length; i < array.Length; i++)
         {
            array[i] = i * 2;
         }
         
         Console.WriteLine("Writer: release");
         semaphore.Release();
      }).ContinueWith(_ => WriterFuncAsync()); // loop; creates a new task (overhead)
   }

   static void ReaderFunc()
   {
      semaphore.WaitAsync().ContinueWith(_ =>
      {
         Console.WriteLine("Reader: obtain");
         
         for(int i = length; i >= 0; i--)
         {
            array[i] = 0;
         }
         
         Console.WriteLine("Reader: release");
         semaphore.Release();
      }).ContinueWith(_ => WriterFuncAsync()); // loop; creates a new task (overhead)
   }
}
  • optimal for very short lived locks
  • prefer *Slim synchonization objects, because they implement spin locks
private var spinLock = new SpinLock();

private void DoWork()
{
   bool isSpinLockTaken = false;
   try
   {
      spinLock.Enter(ref isSpinLockTaken);
      
      // ...
   }
   finally
   {
      if(isSpinLockTaken)
      {
         spinLock.Exit();
      }
   }
}

ReaderWriterLock

[Bearbeiten | Quelltext bearbeiten]
  • DEPRECATED! DO NOT USE!


  • can be shared between multiple processes
  • more expensive than Monitor
  • identified by a name
using (var mutex = new Mutex (false, "MyMutexName"))
{
   // try to get a lock within three seconds
   if (!mutex.WaitOne (TimeSpan.FromSeconds(3), false))
   {
     // ...
   }
}

Concurrent Collections

[Bearbeiten | Quelltext bearbeiten]
  • may harm performance, because every access causes locking
  • use non-locking collection when reading/writing multiple values at once; use locking at higher level
  • prefer non-locking algorithms
ConcurrentBag<T> unordered collection
ConcurrentDictionary<TKey, TValue> key/value pairs
ConcurrentQueue<T> FIFO
ConcurrentStack<T> LIFO

Higher-Level Abstractions

[Bearbeiten | Quelltext bearbeiten]

Replace Entire Collection

[Bearbeiten | Quelltext bearbeiten]
  • use when access is mostly read-only
  • use ImmutableCollections to minimize garbage
  • clients may need to store object in a local variable to have a fixed reference
// volatile ensures that reads from multiple threads have latest version
// by preventing code reordering 
private volatile Dictionary<string, MyComplexObject> _data = new Dictionary<string, MyComplexObject();

public Dictionary<string, MyComplexObject> Data
{
   get
   {
      return _data;
   }
}

private void UpdateData()
{
   var newData = new Dictionary<string, MyComplexObject>();
   newData["Foo"] = new MyComplexObject();
   // ...
   data = newData;
}

Copy Resource Per-Thread

[Bearbeiten | Quelltext bearbeiten]
  • only for lightweight class
  • for classes that are non-thread save
  • simply add [ThreadStatic] attribute
  • always assume that it might not be initialized at first within the current thread context
[ThreadStatic]
static Random _safeRand;

static void Main()
{
   var results = new int[100];
   
   Parallel.For(0, 5000, i => 
   {
      if(saveRand == null)
      {
         _safeRand = new Random();
      }
      
      var randomNumber = _safeRand.Next(100);
      Interlocked.Increment(ref results[randomNumber]); 
   });
}