Download presentation
Presentation is loading. Please wait.
1
Thread Synchronization Primitives
Microsoft Virtual Academy Header Advanced .NET Threading, Part 4 Thread Synchronization Primitives Jeffrey Richter Produced by
2
Thread Synchronization Mindset
Programming .NET Framework Applications with C# Thread Synchronization Mindset Synchronization: Prevents corruption when 2+ threads access shared data at the same time Error prone: Thread voluntarily requests permission to access data Bad perf: requesting permission is slow & other threads stop running Increase overhead: Blocking threads cause creation of new threads Prefer unshared objects: heap objects, read-only objects, value types private sealed class LinkedList { private SomeKindOfLock m_lock = new SomeKindOfLock(); private Node m_head; public void Add(Node newNode) { m_lock.Acquire(); // Minimize the perf hit of this // This is very fast (2 reference assignments) newNode.m_next = m_head; m_head = newNode; m_lock.Release(); // and this } } Copyright (c) by Wintellect, LLC (Unauthorized duplication prohibited)
3
Class Libraries & Thread Safety
Programming .NET Framework Applications with C# Class Libraries & Thread Safety FCL guarantees static methods are thread safe Required because users can’t ensure thread safety otherwise This doesn’t mean that every method takes a lock It means that no state can be corrupted if called by multiple threads at same time FCL doesn’t guarantee instance methods are thread safe Better performance An object isn’t accessible by other threads unless a reference to the object is passed to other threads Of course, if object’s purpose is to synchronize threads, then the methods are thread safe Your own class libraries should mimic these rules Copyright (c) by Wintellect, LLC (Unauthorized duplication prohibited)
4
Agenda The special user-mode CPU instructions which coordinate threads running concurrently on different CPUs These are the fastest thread synchronization primitives available All other thread synchronization constructs use these internally The Evolution of Thread Synchronization Locks I’ll take you on a journey exploring how locks have improved their performance & memory consumption over time
5
The CLR’s Memory Model: Volatile Methods
6
Code Optimizations Wreak Havoc with Multiple Threads
// Compile with "/platform:x86 /o" and run it NOT under the debugger internal static class StrangeBehavior { private static Boolean s_stopWorker = false; public static void Main() { Console.WriteLine("Main: letting worker run for 5 seconds"); Thread t = new Thread(Worker); t.Start(); Thread.Sleep(5000); s_stopWorker = true; Console.WriteLine("Main: waiting for worker to stop"); t.Join(); } private static void Worker(Object o) { Int32 x = 0; while (!s_stopWorker) x++; Console.WriteLine("Worker: x={0}", x); } } // Compiler optimizes to this: Int32 x = 0; if (!s_stopWorker) while (true) x++; // Faster! Console.WriteLine("Worker: x = {0}", x);
7
Compiler Optimizations
8
Code Optimizations Cause Problems
class OutOfProgramOrder { private Boolean m_flag = false; private Int32 m_value = 0; public void Thread1() { // These could execute in reverse order m_value = 5; m_flag = true; } public void Thread2() { // m_value could be read before m_flag if (m_flag) Display(m_value); // Nothing or 5? } }
9
The Volatile Write/Read Methods (ECMA-335, Partition I, Section 12.6)
Methods tell compilers (C#/JIT) & CPU to turn off some optimizations Data cannot be cached in a register or temporary variable, also… Volatile.Write: earlier program-order loads/stores must occur before Volatile.Read: later program-order loads/stores must occur after Summary: When threads “communicate” using shared memory Write last byte volatile & read first byte volatile public static class Volatile { public static void Write(ref Int32 location, Int32 value); public static Int32 Read(ref Int32 location); public static void Write<T>(ref T location, T value) where T : class; public static T Read<T> (ref T location) where T : class; // Other types: Boolean, (S)Byte, (U)Int16, UInt32, (U)Int64, // (U)IntPtr, Single/Double }
10
Fix with Volatile Write/Read Methods
class OutOfProgramOrder { private Boolean m_flag = false; private Int32 m_value = 0; public void Thread1() { // m_value must be written before m_flag m_value = 5; Volatile.Write(ref m_flag, true); } public void Thread2() { // m_value must be read after m_flag if (Volatile.Read(ref m_flag)) Display(m_value); // Nothing or 5! } }
11
Fix by Applying C#’s ‘volatile’ Keyword to Fields
class OutOfProgramOrder { // Allowed volatile field types: (S)Byte, (U)Int16, (U)Int32, // Char, Single, Boolean, enums, & references private volatile Boolean m_flag = false; private Int32 m_value = 0; public void Thread1() { // m_value must be written before m_flag m_value = 5; m_flag = true; // volatile write } public void Thread2() { // m_value must be read after m_flag if (m_flag) // volatile read Display(m_value); // Nothing or 5! } }
12
My Advice: Avoid the ‘volatile’ Keyword
Programming .NET Framework Applications with C# My Advice: Avoid the ‘volatile’ Keyword You want Volatile.Write/Read calls to be red flags Fortunately, Visual Basic.NET does not support volatile fields Making all accesses volatile hurts performance Volatile fields aren’t volatile when passed by reference: // If m_amount is volatile, // this is 2 volatile reads & 1 volatile write: m_amount = m_amount + m_amount; Boolean success = Int32.TryParse("123", out m_amount); // The above line causes the C# compiler to generate a warning: // CS0420: a reference to a volatile field will not be // treated as volatile s_x = s_x + s_x; // When not volatile mov r11d,dword ptr [FFED38B4h] add r11d,r11d mov dword ptr [FFED38B4h],r11d s_x = s_x + s_x; // When volatile mov r11d,dword ptr [FFED38B0h] mov eax,dword ptr [FFED38B0h] add r11d,eax mov dword ptr [FFED38B0h],r11d Copyright (c) by Wintellect, LLC (Unauthorized duplication prohibited)
13
Interlocked Methods
14
Manipulating an Int32 Atomically
Programming .NET Framework Applications with C# Manipulating an Int32 Atomically public static class Interlocked { // return (++location); public static Int32 Increment(ref Int32 location); // return (–-location); public static Int32 Decrement(ref Int32 location); // return (location += value); // Note: value can be a negative number allowing subtraction public static Int32 Add(ref Int32 location, Int32 value); // Int32 old = location; location = value; return old; public static Int32 Exchange(ref Int32 location, Int32 value); // Int32 old = location; // if (location == comparand) location = value // return old; public static Int32 CompareExchange(ref Int32 location, Int32 value, Int32 comparand); } Each Interlocked method is a full memory fence Copyright (c) by Wintellect, LLC (Unauthorized duplication prohibited)
15
The AsyncCoordinator uses Interlocked APIs to Coordinate Completion/Cancel/Timeout
internal sealed class MultiWebRequests { AsyncCoordinator m_ac = new AsyncCoordinator(); private Dictionary<String, Object> m_servers = new Dictionary<String, Object> { { " null }, { " null } }; public MultiWebRequests() { var client = new HttpClient(); foreach (var server in m_servers.Keys.ToArray()) { m_ac.AboutToBegin(1); client.GetByteArrayAsync(server).ContinueWith(t => RequestDone(server, t)); } m_ac.AllBegun(AllDone, 5000); // 5 second timeout private void RequestDone(String server, Task<Byte[]> task) { // NOTE: Error handling omitted m_servers[server] = task.Result; m_ac.JustEnded(); private void AllDone(Status status) { ... }
16
AsyncCoordinator public sealed class AsyncCoordinator { private Int32 m_ops = 1; // Decremented by AllBegun private Int32 m_invoked = 0; // 0=false; 1=true private Action<Status> m_callback; // Delegate private Timer m_timer; public void AboutToBegin(Int32 opsToAdd) { Interlocked.Add(ref m_ops, opsToAdd); } public void AllBegun(Action<Status> callback, Int32 timeout) { m_callback = callback; m_timer = new Timer(TimeExpired, null, timeout, Timeout.Infinite); JustEnded(); } public void JustEnded() { if (Interlocked.Decrement(ref m_ops) == 0) Invoke(Status.AllDone); private void TimeExpired(Object state) { Invoke(Status.Timeout); } public void Cancel() { Invoke(Status.Cancel); } private void Invoke(Status status) { if (Interlocked.Exchange(ref m_invoked, 1) == 0) m_callback(status);
17
Interlocked Any Operation Pattern (Optimistic Concurrency)
Programming .NET Framework Applications with C# Interlocked Any Operation Pattern (Optimistic Concurrency) public static Int32 InterlockMax(ref Int32 target, Int32 value) { Int32 targetVal = target, startVal, desiredVal; // Don’t access target in the loop except in an attempt // to change it because another thread may be touching it do { // Record this iteration’s starting value startVal = targetVal; // Calculate desired value in terms of startVal & value desiredVal = Math.Max(startVal, value); (NOTE: The thread could be pre-empted here) // If target’s content changed behind our thread’s back; do not change target // else, set target to desiredVal; targetVal set to what CE saw targetVal = Interlocked.CompareExchange(ref target, desiredVal, startVal); // If CE detected changed value during this iteration, try again } while (startVal != targetVal); // Return what this thread did to target return desiredVal; } T=5 V=3 TV=5 SV=5 DV=5 T=5 V=3 TV=1 SV=1 DV=3 T=5 V=3 TV=5 SV=1 DV=3 T=1 V=3 TV=1 SV=1 DV=3 Copyright (c) by Wintellect, LLC (Unauthorized duplication prohibited)
18
Programming .NET Framework Applications with C#
SimpleSpinLock // Simple version of .NET 4.0’s System.Threading.SpinLock public struct SimpleSpinLock { private Int32 m_ResourceInUse; // 0=false (default), 1=true public void Enter() { // Always set resource to in-use; if it was not in-use, return while (Interlocked.Exchange(ref m_resourceInUse, 1) == 1) ; } public void Leave() { // Set resource to not in-use Volatile.Write(ref m_ResourceInUse, 0); } } Copyright (c) by Wintellect, LLC (Unauthorized duplication prohibited)
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.