C#多线程编程实战(二)
1.1 简介
为了防止一个应用程序控制CPU而导致其他应用程序和操作系统本身永远被挂起这一可能情况,操作系统不得不使用某种方式将物理计算分割为一些虚拟的进程,并给予每个执行程序一定量的计算能力。此外操作系统必须始终能够优先访问CPU,并能调整不同程序访问CPU的优先级。线程正式这一慨念的实现。
多线程优点:可以同时执行多个计算任务,有可能提高计算机的处理能力,使得计算机每秒能执行越来越多的命令
多线程缺点:消耗大量的操作系统资源。多个线程共享一个处理器将导致操作系统忙于管理这些线程,而无法运行程序。
1.2 创建线程
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart(PrintNumbers));//无参数的委托 t1.Start(); Thread t2 = new Thread(new ParameterizedThreadStart(PrintNumbers));//有参数的委托 t2.Start(10); Console.ReadLine(); } static void PrintNumbers() { Console.WriteLine("Starting..."); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } } //注意:要使用ParameterizedThreadStart,定义的参数必须为object static void PrintNumbers(object count) { Console.WriteLine("Starting..."); for (int i = 0; i < Convert.ToInt32(count); i++) { Console.WriteLine(i); } } } }
注释:我们只需指定在不同线程运行的方法名,而C#编译器会在后台创建这些对象
1.3 暂停线程
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Thread t1 = new Thread(PrintNumbersWithDelay); t1.Start(); PrintNumbers(); Console.ReadLine(); } static void PrintNumbers() { Console.WriteLine("Starting..."); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } } static void PrintNumbersWithDelay() { Console.WriteLine("Starting..."); for (int i = 0; i < 10; i++) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine(i); } } } }
注释:使用Thread.Sleep(TimeSpan.FromSeconds(2));暂停线程
1.4 线程等待
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Console.WriteLine("Starting..."); Thread t = new Thread(PrintNumbersWithDelay); t.Start(); t.Join(); //使用Join等待t完成 PrintNumbers(); Console.WriteLine("THread Complete"); Console.ReadLine(); } static void PrintNumbers() { Console.WriteLine("Starting..."); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } } static void PrintNumbersWithDelay() { Console.WriteLine("Starting..."); for (int i = 0; i < 10; i++) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine(i); } } } }
注释:使用t.Join(); 等待t完成
1.5 终止线程
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Console.WriteLine("Starting Program..."); Thread t1 = new Thread(PrintNumbersWithDelay); t1.Start(); Thread.Sleep(TimeSpan.FromSeconds(6)); t1.Abort(); //使用Abort()终止线程 Console.WriteLine("Thread t1 has been aborted"); Thread t2 = new Thread(PrintNumbers); PrintNumbers(); Console.ReadLine(); } static void PrintNumbers() { Console.WriteLine("Starting..."); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } } static void PrintNumbersWithDelay() { Console.WriteLine("Starting..."); for (int i = 0; i < 10; i++) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine(i); } } } }
注释:使用Thread实例的Abort方法终止线程
1.6 检测线程状态
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Console.WriteLine("Start Program..."); Thread t1 = new Thread(PrintNumbersWithStatus); Thread t2 = new Thread(DoNothing); Console.WriteLine(t1.ThreadState.ToString());//获取实例线程状态 t2.Start(); t1.Start(); for (int i = 0; i < 30; i++) { Console.WriteLine(t1.ThreadState.ToString()); } Thread.Sleep(TimeSpan.FromSeconds(6)); t1.Abort(); Console.WriteLine("thread t1 has been aborted"); Console.WriteLine(t1.ThreadState.ToString()); Console.WriteLine(t2.ThreadState.ToString()); Console.ReadLine(); } private static void PrintNumbersWithStatus() { Console.WriteLine("Starting..."); Console.WriteLine(Thread.CurrentThread.ThreadState.ToString());//获取当前线程状态 for (int i = 0; i < 10; i++) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine(i); } } private static void DoNothing() { Thread.Sleep(TimeSpan.FromSeconds(2)); } } }
注释:使用Thread.ThreadState获取线程的运行状态。ThreadState是一个C#枚举。谨记:不要在程序中使用线程终止,否则可能会出现意想不到的结果
1.7 线程优先级
using System; using System.Diagnostics; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Console.WriteLine($"Current thread priority: {Thread.CurrentThread.Priority}"); Console.WriteLine("Running on all cores available");//获取实例线程状态 RunThreads(); Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("Running on a single Core"); //让操作系统的所有线程运行在单个CPU核心上 Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1); RunThreads(); Console.ReadLine(); } private static void RunThreads() { var sample = new ThreadSample(); var t1 = new Thread(sample.CountNumbers); t1.Name = "Thread One"; var t2 = new Thread(sample.CountNumbers); t2.Name = "Thread Two"; t1.Priority = ThreadPriority.Highest;//使用Priority设置线程的优先级 t2.Priority = ThreadPriority.Lowest; t1.Start(); t2.Start(); Thread.Sleep(TimeSpan.FromSeconds(2)); sample.Stop(); } } class ThreadSample { private bool _isStopped = false; public void Stop() { _isStopped = true; } public void CountNumbers() { long counter = 0; while (!_isStopped) { counter++; } Console.WriteLine($"{Thread.CurrentThread.Name} with {Thread.CurrentThread.Priority} priority has a count={counter.ToString("N0")}"); } } }
注释:单核执行多线程耗费的时间比多核的多很多
1.8 前台线程和后台线程
using System; using System.Diagnostics; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { var sampleForground = new ThreadSample(10); var sampleBackground = new ThreadSample(20); var t1 = new Thread(sampleForground.CountNumbers); t1.Name = "ForegroundThread"; //没有明确声明的均为前台线程 var t2 = new Thread(sampleBackground.CountNumbers); t2.Name = "BackgroundThread"; t2.IsBackground = true; //设置为后台线程 t1.Start(); t2.Start(); } } class ThreadSample { private readonly int _iteration; public ThreadSample(int iteration) { _iteration = iteration; } public void CountNumbers() { for (int i = 0; i < _iteration; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}"); } } } }
注释:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作
1.9 向线程传递参数
using System; using System.Diagnostics; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { ThreadSample sample = new ThreadSample(5); Thread t1 = new Thread(sample.CountNumbers); t1.Name = "ThreadOne"; t1.Start(); t1.Join(); Console.WriteLine("--------------------------"); Thread t2 = new Thread(Count); t2.Name = "ThreadTwo"; t2.Start(3); t2.Join(); Console.WriteLine("--------------------------"); //使用lambda表达式引用另一个C#对方的方式被称为闭包。当在lambda表达式中使用任何局部变量时,C#会生成一个类,并将该变量作为该类的一个属性,但是我们无须定义该类,C#编译器会自动帮我们实现 Thread t3 = new Thread(()=> CountNumbers(5)); t3.Name = "ThreadThree"; t3.Start(); t3.Join(); Console.WriteLine("--------------------------"); int i = 10; Thread t4 = new Thread(() => PrintNumber(i)); i = 20; Thread t5 = new Thread(() => PrintNumber(i)); t4.Start(); t5.Start(); //t4, t5都会输出20, 因为t4,t5没有Start之前i已经变成20了 Console.ReadKey(); } static void Count(object iterations) { CountNumbers((int)iterations); } static void CountNumbers(int iterations) { for (int i = 1; i <= iterations; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}"); } } static void PrintNumber(int number) { Console.WriteLine(number); } } class ThreadSample { private readonly int _iteration; public ThreadSample(int iteration) { _iteration = iteration; } public void CountNumbers() { for (int i = 1; i <= _iteration; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}"); } } } }
注释:也可以使用ThreadStart传递参数
1.10 使用C# lock关键字
using System; using System.Diagnostics; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Console.WriteLine("Incorrect Counter"); Counter c1 = new Counter(); var t1 = new Thread(() => TestCounter(c1)); var t2 = new Thread(() => TestCounter(c1)); var t3 = new Thread(() => TestCounter(c1)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine($"Total Count: {c1.Count}"); Console.WriteLine("------------------------"); Console.WriteLine("Correct counter"); CounterWithLock c2 = new CounterWithLock(); t1 = new Thread(() => TestCounter(c2)); t2 = new Thread(() => TestCounter(c2)); t3 = new Thread(() => TestCounter(c2)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine($"Total count:{c2.Count}"); Console.ReadLine(); } static void TestCounter(CounterBase c) { for (int i = 0; i < 100000; i++) { c.Increment(); c.Decrement(); } } class Counter : CounterBase { public int Count { get; private set; } public override void Decrement() { Count--; } public override void Increment() { Count++; } } class CounterWithLock : CounterBase { private readonly object _asyncRoot = new object(); public int Count { get; private set; } public override void Decrement() { lock (_asyncRoot) { Count--; } } public override void Increment() { lock (_asyncRoot) { Count++; } } } abstract class CounterBase { public abstract void Increment(); public abstract void Decrement(); } } class ThreadSample { private readonly int _iteration; public ThreadSample(int iteration) { _iteration = iteration; } public void CountNumbers() { for (int i = 1; i <= _iteration; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}"); } } } }
注释:不加锁,得出的结果不确定,竞争条件下很容易出错。加锁得出的结果是正确的,但是性能受到了影响
1.11 使用Monitor类锁定资源
using System; using System.Diagnostics; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { object lock1 = new object(); object lock2 = new object(); new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock (lock2) { Thread.Sleep(1000); Console.WriteLine("Monitor.TryEnter allows not to get stuck, returning false after a specified timeout is elapsed"); //直接使用Monitor.TryEnter, 如果在第二个参数之前还未获取到lock保护的资源会返回false if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5))) { Console.WriteLine("Acquired a protected resource successfully"); } else { Console.WriteLine("Timeout acquiring a resource"); } } new Thread(() => LockTooMuch(lock1, lock2)).Start(); Console.WriteLine("-----------------------------"); /* 下面代码会造成死锁, 所以注释掉 lock (lock2) { Console.WriteLine("This will be a deadlock!"); Thread.Sleep(1000); lock (lock1) { Console.WriteLine("Acquired a protected resource successfully"); } } */ } static void LockTooMuch(object lock1, object lock2) { lock (lock1) { Thread.Sleep(1000); lock (lock2); } } } }
注释:Monitor.TryEnter在指定的时间内尝试获取指定对象上的排他锁
1.12 处理异常
using System; using System.Diagnostics; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Thread t = new Thread(FaultyThread); t.Start(); t.Join(); try { t = new Thread(BadFaultyThread); t.Start(); } catch (Exception ex) { Console.WriteLine("We won't get here"); } } static void BadFaultyThread() { Console.WriteLine("Starting a faulty thread....."); Thread.Sleep(TimeSpan.FromSeconds(2)); //这个异常主线程无法捕捉到,因为是在子线程抛出的异常。需要在子线程中加入try...catch捕获异常 throw new Exception("Boom!"); } static void FaultyThread() { try { Console.WriteLine("Starting a faulty thread..."); Thread.Sleep(TimeSpan.FromSeconds(1)); throw new Exception("Boom"); } catch (Exception ex) { Console.WriteLine($"Exception handled: {ex.Message}"); } } } }
C#多线程编程实战 (线程同步)
2.1 简介
竞争条件:多个线程同时使用共享对象。需要同步这些线程使得共享对象的操作能够以正确的顺序执行
线程同步问题:多线程的执行并没有正确的同步,当一个线程执行递增和递减操作时,其他线程需要依次等待
线程同步解决方案:
无须共享对象:大部分时候可以通过重新设计来移除共享对象,去掉复杂的同步构造,避免多线程使用单一对象
必须共享对象:只使用原子操作,一个操作只占用一个量子的时间,无须实现其他线程等待当前操作完成
内核模式:将等待的线程置于阻塞状态,消耗少量的CPU资源,但会引入至少一次上下文切换,适用于线程等待较长时间
用户模式:只是简单的等待,线程等待会浪费CPU时间但是可以节省上下文切换消耗的CPU时间,适用于线程等待较短时间
混合模式:先尝试用户模式,如果等待时间较长,则会切换到内核模式
2.2 执行基本的原子操作
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Console.WriteLine("Incorrect counter"); //没有限定,会遇到竞争条件,得出的结果大部分不是正确的 var c = new Counter(); var t1 = new Thread(() => TestCounter(c)); var t2 = new Thread(() => TestCounter(c)); var t3 = new Thread(() => TestCounter(c)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine($"Total count:{c.Count}"); Console.WriteLine("--------------------------"); Console.WriteLine("Correct counter"); //使用Interlocked类提供的原子操作方法,无需锁定任何对象可得出正确结果 var c1 = new CounterNoLock(); t1 = new Thread(() => TestCounter(c1)); t2 = new Thread(() => TestCounter(c1)); t3 = new Thread(() => TestCounter(c1)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine($"Total count:{c1.Count}"); Console.ReadLine(); } static void TestCounter(CounterBase c) { for (int i = 0; i < 100000; i++) { c.Increment(); c.Decrement(); } } class Counter : CounterBase { private int _count; public int Count { get { return _count; } } public override void Decrement() { _count--; } public override void Increment() { _count++; } } class CounterNoLock : CounterBase { private int _count; public int Count { get { return _count; } } public override void Decrement() { //Interlocked提供了Increment()、Decrement()和Add等基本数学操作的原子方法 Interlocked.Decrement(ref _count); } public override void Increment() { Interlocked.Increment(ref _count); } } abstract class CounterBase { public abstract void Increment(); public abstract void Decrement(); } } }
注释:Interlocked提供了Increment()、Decrement()和Add等基本数学操作的原子方法,不用锁也可以得出正确结果
2.3 使用Mutex类
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { const string MutexName = "CSharpThreadingCookbook"; //Mutex是一种原始的同步方式,只对一个线程授予对共享资源的独占访问 //定义一个指定名称的互斥量,设置initialOwner标志为false using (var m = new Mutex(false, MutexName)) { //如果互斥量已经被创建,获取互斥量,否则就执行else语句 if (!m.WaitOne(TimeSpan.FromSeconds(5), false)) { Console.WriteLine("Second instance is running!"); } else { Console.WriteLine("Running!"); Console.ReadLine(); m.ReleaseMutex(); } } //如果再运行同样的程序,则会在5秒内尝试获取互斥量,如果第一个程序按下了任何键,第二个程序开始执行。 //如果等待5秒钟,第二个程序将无法获取该互斥量 } } }
注释:互斥量是全局操作对象,必须正确关闭,最好用using
2.4 使用SemaphoreSlim类
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { //启动6个线程,启动的顺序不一样 for (int i = 0; i <= 6; i++) { string threadName = "Thread " + i; int secondsToWait = 2 + 2 * i; var t = new Thread(() => AccessDatabase(threadName, secondsToWait)); t.Start(); } Console.ReadLine(); } //SemaphoreSlim的构造函数参数为允许的并发线程数目 static SemaphoreSlim semaphore = new SemaphoreSlim(4); static void AccessDatabase(string name, int seconds) { Console.WriteLine($"{name} waits to access a database"); semaphore.Wait(); Console.WriteLine($"{name} was granted an access to a database"); Thread.Sleep(TimeSpan.FromSeconds(seconds)); Console.WriteLine($"{name} is Completed"); //调用Release方法说明线程已经完成,可以开启一个新的线程了 semaphore.Release(); } } }
注释:这里使用了混合模式,允许我们在等待时间很短的情况下无需上下文切换。SemaphoreSlim并不使用Windows内核信号量,而且也不支持进程间同步
2.5 使用AutoResetEvent类
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Thread t = new Thread(() => Process(10)); t.Start(); Console.WriteLine("Waiting for another thread to complete work"); //开启一个线程后workEvent等待,直到收到Set信号 workEvent.WaitOne(); Console.WriteLine("First operation is complete"); Console.WriteLine("Performing an operation on a main thread"); Thread.Sleep(TimeSpan.FromSeconds(5)); mainEvent.Set(); Console.WriteLine("Now running the second operation on a second thread"); workEvent.WaitOne(); Console.WriteLine("Second operation is complete"); Console.ReadLine(); } //初始状态为unsignaled,子线程向主线程发信号 private static AutoResetEvent workEvent = new AutoResetEvent(false); //初始状态为unsignaled,主线程向子线程发信号 private static AutoResetEvent mainEvent = new AutoResetEvent(false); static void Process(int seconds) { Console.WriteLine("Starting a long running work..."); Thread.Sleep(TimeSpan.FromSeconds(seconds)); Console.WriteLine("Work is done!"); workEvent.Set();//将事件设为终止状态允许一个或多个线程继续 Console.WriteLine("Waiting for a main thread to complete its work"); mainEvent.WaitOne();//阻止当前线程,直到mainEvent收到信号 Console.WriteLine("Starting second operation..."); Thread.Sleep(TimeSpan.FromSeconds(seconds)); Console.WriteLine("Work is done"); workEvent.Set(); } } }
注释:AutoResetEvent采用的是内核模式,所以等待时间不能太长
2.6 使用ManualResetEventSlim类
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Thread t1 = new Thread(() => TravelThroughGates("Thread 1", 5)); Thread t2 = new Thread(() => TravelThroughGates("Thread 2", 6)); Thread t3 = new Thread(() => TravelThroughGates("Thread 3", 12)); t1.Start(); t2.Start(); t3.Start(); Thread.Sleep(TimeSpan.FromSeconds(6)); Console.WriteLine("The gates are now open"); mainEvent.Set();//将时间设置为有信号,从而让一个或多个等待该事件的线程继续 Thread.Sleep(TimeSpan.FromSeconds(2)); mainEvent.Reset();//将事件设置为非终止,从而导致线程受阻 Console.WriteLine("The gates have been closed!"); Thread.Sleep(TimeSpan.FromSeconds(10)); Console.WriteLine("The gates are now open for the second time"); mainEvent.Set(); Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("The gates have been closed!"); mainEvent.Reset(); Console.ReadLine(); } static ManualResetEventSlim mainEvent = new ManualResetEventSlim(false); static void TravelThroughGates(string threadName, int seconds) { Console.WriteLine($"{threadName} falls to sleep"); Thread.Sleep(TimeSpan.FromSeconds(seconds)); Console.WriteLine($"{threadName} waits for the gates to open!"); mainEvent.Wait();//阻止当前线程 Console.WriteLine($"{threadName} enter the gates!"); } } }
注释:ManualResetEventSlim工作方式像人群通过的大门,一直保持大门敞开直到调用reset,set相当于打开大门,reset相当于关闭大门
2.7 使用CountDownEvent类
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Console.WriteLine("Starting two operations"); Thread t1 = new Thread(() => PerformOperation("Operation 1 is completed", 4)); Thread t2 = new Thread(() => PerformOperation("Operation 2 is completed", 8)); t1.Start(); t2.Start(); //开启了两个线程,调用Wait方法阻止当前线程,知道所有线程都完成 countdown.Wait(); Console.WriteLine("Both operations have been completed"); countdown.Dispose(); Console.ReadLine(); } //计数器初始化CountdownEvent实例,计数器表示:当计数器个数完成操作发出信号 static CountdownEvent countdown = new CountdownEvent(2); static void PerformOperation(string message, int seconds) { Thread.Sleep(TimeSpan.FromSeconds(seconds)); Console.WriteLine(message); //向CountdownEvent注册信息,并减少当前计数器数值 countdown.Signal(); } } }
注释:如果Signal方法没有达到指定的次数,那么countdown.wait()会一直等待,所以请确保所有线程完成后都要调用Signal方法
2.8 使用Barrier类
using System; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Thread t1 = new Thread(() => PlayMusic("the gutarist", "play an amazing solo", 5)); Thread t2 = new Thread(() => PlayMusic("the signer", "sing his song", 2)); t1.Start(); t2.Start(); Console.ReadLine(); } //后面的Lamda表达式是回调函数。执行完SignalAndWait后执行 static Barrier barrier = new Barrier(2, b=>Console.WriteLine($"End of phase {b.CurrentPhaseNumber + 1}")); static void PlayMusic(string name, string message, int seconds) { for (int i = 0; i < 3; i++) { Console.WriteLine("==========================="); Thread.Sleep(TimeSpan.FromSeconds(seconds)); Console.WriteLine($"{name} starts to {message}"); Thread.Sleep(TimeSpan.FromSeconds(seconds)); Console.WriteLine($"{name} finishes to {message}"); //等所有调用线程都结束 barrier.SignalAndWait(); } } } }
注释:
2.9 使用ReaderWriterlockSlim类
using System; using System.Collections.Generic; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { new Thread(Read) { IsBackground = true }.Start(); new Thread(Read) { IsBackground = true }.Start(); new Thread(Read) { IsBackground = true }.Start(); new Thread(() => Write("Thread 1")) { IsBackground = true }.Start(); new Thread(() => Write("Thread 2")) { IsBackground = true }.Start(); Thread.Sleep(TimeSpan.FromSeconds(30)); Console.ReadLine(); } //实现线程安全 static ReaderWriterLockSlim _rw = new ReaderWriterLockSlim(); static Dictionary<int, int> items = new Dictionary<int, int>(); static void Read() { Console.WriteLine("Reading contents of a dictionary"); while (true) { try { //读锁 _rw.EnterReadLock(); foreach (var key in items.Keys) { Thread.Sleep(TimeSpan.FromSeconds(0.1)); } } finally { //计数为0时退出读取模式 _rw.ExitReadLock(); } } } static void Write(string threadName) { while (true) { try { int newKey = new Random().Next(250); _rw.EnterUpgradeableReadLock(); if (!items.ContainsKey(newKey)) { try { //写锁 _rw.EnterWriteLock(); items[newKey] = 1; Console.WriteLine($"New key {newKey} is added to a dictionary by a {threadName}"); } finally { //计数为0时退出写入模式 _rw.ExitWriteLock(); } } Thread.Sleep(TimeSpan.FromSeconds(0.1)); } finally { //计数为0时退出可升级模式 _rw.ExitUpgradeableReadLock(); } } } } }
注释:从集合读取数据时,根据当前数据决定是否获取一个写锁并修改该集合。获取写锁后集合会处于阻塞状态。
2.10 使用SpinWait类
using System; using System.Collections.Generic; using System.Threading; namespace MulityThreadNote { class Program { static void Main(string[] args) { Thread t1 = new Thread(UserModeWait); Thread t2 = new Thread(HybridSpinWait); Console.WriteLine("Running user mode waiting"); t1.Start(); Thread.Sleep(20); _isComplete = true; Thread.Sleep(TimeSpan.FromSeconds(1)); _isComplete = false; Console.WriteLine("Running hybrid SpinWait construct waiting"); t2.Start(); Thread.Sleep(5); _isComplete = true; Console.ReadLine(); } //volatile 一个字段可能会被多个线程同时修改,不会被编译器和处理器优化为只能被单个线程访问 static volatile bool _isComplete = false; static void UserModeWait() { while (!_isComplete) { Console.WriteLine("."); } Console.WriteLine(); Console.WriteLine("Waiting is complete"); } static void HybridSpinWait() { var w = new SpinWait(); while (!_isComplete) { //执行单一自旋 w.SpinOnce(); //NextSpinWillYield:获取对SpinOnce的下一次调用是否将产生处理,同时触发强制上下文切换 //显示线程是否切换为阻塞状态 Console.WriteLine(w.NextSpinWillYield); } Console.WriteLine("Waiting is complete"); } } }