Benutzer:MovGP0/Parallel/Locking
Zur Navigation springen
Zur Suche springen
MovGP0 | Über mich | Hilfen | Artikel | Weblinks | Literatur | Zitate | Notizen | Programmierung | MSCert | Physik |
Locking[Bearbeiten | Quelltext bearbeiten]Performance[Bearbeiten | Quelltext bearbeiten]Locks block other threads. Try to avoid them. Execution Speed:
Lock object[Bearbeiten | Quelltext bearbeiten]
[Bearbeiten | Quelltext bearbeiten] |
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;
}
}
}
}
|
Interlocked
[Bearbeiten | Quelltext bearbeiten]- Methods from the static
Interlocked
class are considered atomic
- Important Interlocked-Methods
- Add
- CompareAndExchange
- Increment
- Decrement
- Exchange
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
- uses
- 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;
}
}
}
}
Monitor
[Bearbeiten | Quelltext bearbeiten]- 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)
}
}
|
Spin Lock
[Bearbeiten | Quelltext bearbeiten]- 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!
Mutex
[Bearbeiten | Quelltext bearbeiten]- 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]);
});
}