FCL不保证实例要领是线程平安的。因为如果悉数增加锁定,会形成机能的庞大丧失。别的,如果每一个实例要领都须要猎取和开释一个锁,现实上会形成终究在任何给定的时候,你的应用程序只要一个线程在运转,这对机能的影响不言而喻。
下面引见基元线程同步组织。
基元:是指可以在代码中运用的最简朴的组织。有两种基元组织:用户形式(user-mode)和内核形式(kernel-mode)。
用户形式
运用了特别的CPU指令来谐和线程。
手艺:volatile关键字、Interlocked类(互锁)、spinlock(自旋锁)
罕见锁①:volatile 关键字指导一个字段可以由多个同时实行的线程修正。 声明为 volatile 的字段不受编译器优化(假定由单个线程接见)的限定。 如许可以确保该字段在任何时候显现的都是最新的值。
Interlocked类: 为多个线程同享的变量供应原子操纵。。所谓原子操纵是指不会被线程调理机制打断的操纵;这类操纵一旦最先,就一向运转到完毕,中心不会有任何 context switch (切换到另一个线程)。
罕见锁②:SpinLock 构造是一个初级别的互斥同步基元,它在守候猎取锁时举行扭转。在多核盘算机上,当守候时候估计较短且少少涌现争用状况时,SpinLock 的机能将高于其他范例的锁。纵然 SpinLock 未猎取锁,它也会发作线程的时候片。 它如许做是为了防备线程优先级别反转,并使垃圾接纳器可以继承实行。 在运用 SpinLock 时,请确保任何线程持有锁的时候不会凌驾一个非常短的时候段,并确保任何线程在持有锁时不会壅塞。
优点:
应只管运用基元用户形式组织,它们的速率要明显快于内核形式的组织。
谐和线程的在硬件中发作的(所以才这么快)。
然则Microsoft Windows操纵体系永久检测不到一个线程在基元用户形式的组织上壅塞了。
因为在用户形式的基元组织上壅塞的线程池永久不认为已梗塞,所以线程池不会建立新线程来替代这类暂时的线程。
这些CPU指令只壅塞线程相称短的时候。
瑕玷:
只要Windows操纵体系内核才住手一个线程的运转(防备它糟蹋CPU的时候)。
在用户形式中运转的线程能够被体系抢占,但线程会以最快的速率再次调理。
想要获得资本但暂时取不到的线程会一向在用户形式中“自旋”,这能够糟蹋大批的CPU时候。线程一向在一个CPU上运转,我们称为“活锁”(livelock)。
实例:
using System;using System.Threading;public class Worker { // This method is called when the thread is started. public void DoWork() { while (!_shouldStop) { Console.WriteLine("Worker thread: working..."); } Console.WriteLine("Worker thread: terminating gracefully."); } public void RequestStop() { _shouldStop = true; } // Keyword volatile is used as a hint to the compiler that this data // member is accessed by multiple threads. private volatile bool _shouldStop; }public class WorkerThreadExample { static void Main() { // Create the worker thread object. This does not start the thread. Worker workerObject = new Worker(); Thread workerThread = new Thread(workerObject.DoWork); // Start the worker thread. workerThread.Start(); Console.WriteLine("Main thread: starting worker thread..."); // Loop until the worker thread activates. while (!workerThread.IsAlive) ; // Put the main thread to sleep for 1 millisecond to // allow the worker thread to do some work. Thread.Sleep(1); // Request that the worker thread stop itself. workerObject.RequestStop(); // Use the Thread.Join method to block the current thread // until the object's thread terminates. workerThread.Join(); Console.WriteLine("Main thread: worker thread has terminated."); } // Sample output: // Main thread: starting worker thread... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: terminating gracefully. // Main thread: worker thread has terminated.}
内核形式
由Windows操纵体系本身供应的。它们请求在应用程序的线程中挪用有操纵体系内核完成的函数。
手艺:EventWaitHandle(事宜)、Semaphore(信号量)、Mutex(互斥体)
System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.EventWaitHandle System.Threading.Mutex System.Threading.Semaphore
罕见锁③:Mutex 类是 Win32 组织的包装,它可以跨应用程序域边境举行封送处置惩罚,可用于多个守候,而且可用于同步差别历程中的线程。
优点:
线程经由过程内核形式的组织猎取其他线程具有的资本时,Windows会壅塞线程以防备它糟蹋CPU时候。当资本变得可用时,Windows会恢复线程,许可它接见资本。它不会占着一个CPU“自旋”。
可完成本机和托管线程相互之间的同步。
可同步在统一台机械的差别历程中运转的线程。
可应用平安性设置,防备未经受权的账户接见它们。
线程可一向壅塞,直到及协作的一切内核形式组织都可用,或许直到鸠合中的任何内核形式组织可用。
在内核形式的组织上壅塞的线程可指定超时价:指定时候内接见不到愿望的资本,线程就可以消除壅塞并实行其他使命。
瑕玷:
将线程从用户形式切换为内核形式(或许相反)会招致庞大的机能丧失,这正是为何要防备运用内核组织的缘由。别的,线程一向壅塞,会致使“死锁“(deadlock)。
死锁老是因为活锁,因为活锁即糟蹋CPU时候,有糟蹋内存(线程栈等),而死锁只糟蹋内存。
夹杂组织
兼具上面二者的优点。在没有合作的状况下,快而且不会壅塞(就像用户形式)。在有合作的状况,愿望它被操纵体系内核壅塞。
手艺:ManualResetEventSlim类、SemaphoreSlim类、Monitor类、Lock类、ReaderWriterLockSlim类、CountdownEvent类、Barrier类、双检锁.
罕见锁④:Monitor 一般更加可取,因为监视器是特地为 .NET Framework 而设想的,因此它比Mutex可以更好地应用资本。只管 mutex 比监视器更加壮大,然则相对于 Monitor 类,它所须要的互操纵转换更斲丧盘算资本。
罕见锁⑤:运用 lock (C#) 或 SyncLock (Visual Basic) 关键字是Monitor的封装。一般比直接运用 Monitor 类更可取,一方面是因为 lock 或 SyncLock 更简约,另一方面是因为lock 或 SyncLock 确保了纵然受庇护的代码激发非常,也可以开释基本监视器。
罕见锁⑥:ReaderWriterLock 锁,在某些状况下,能够愿望只在写入数据时锁定资本,在不更新数据时许可多个客户端同时读取数据。ReaderWriterLock 类在线程修正资本时将强迫其独有接见资本,但在读取资本时则许可非独有接见。 ReaderWriter 锁可用于替代排它锁。运用排它锁时,纵然其他线程不须要更新数据,也会让这些线程守候。
双检锁
罕见锁⑦:两重搜检锁定形式(也被称为”两重搜检加锁优化”,”锁暗示”(Lock hint)) 是一种软件设想形式用来削减并发体系中合作和同步的开支。
两重搜检锁定形式起首考证锁定前提(第一次搜检),只要经由过程锁定前提考证才真正的举行加锁逻辑并再次考证前提(第二次搜检)。
它一般用于削减加锁开支,尤其是为多线程环境中的单例形式完成“惰性初始化”。惰性初始化的意义是直到第一次接见时才初始化它的值。
public sealed class Singleton { private static volatile Singleton instance = null; private static object syncRoot = new Object(); private static int count = 100; private Singleton() { } public static Singleton Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) instance = new Singleton(); } } return instance; } } public static Singleton GetSingleton() { if (instance == null) { lock (syncRoot) { if (instance == null) { instance = new Singleton(); } } } return instance; } public override string ToString() { lock (syncRoot) { int buf = --count; Thread.Sleep(20); return buf + "_" + count.ToString(); } } }static void Main(string[] args) { Task<string>[] tasks = new Task<string>[10]; Stopwatch watch = new Stopwatch(); object syncRoot = new Object(); watch.Start(); for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Factory.StartNew<string>(() =>(Singleton.GetSingleton().ToString())); } Task.WaitAll(tasks, 5000);//设置超时5s watch.Stop(); Console.WriteLine("Tasks running need " + watch.ElapsedMilliseconds + " ms." + "\n"); for (int i = 0; i < tasks.Length; i++) { //超时处置惩罚 if (tasks[i].Status != TaskStatus.RanToCompletion) { Console.WriteLine("Task {0} Error!", i + 1); } else { //save result Console.WriteLine("Tick ID is " + tasks[i].Result); Console.WriteLine(); } } for (int i = 0; i < 3; i++) { Console.WriteLine("Main thread do work!"); Thread.Sleep(200); } Console.ReadKey(); }
输出:
Tasks running need 298 ms. Tick ID is 96_96 Tick ID is 99_99 Tick ID is 97_97 Tick ID is 98_98 Tick ID is 95_95 Tick ID is 94_94 Tick ID is 93_93 Tick ID is 92_92 Tick ID is 91_91 Tick ID is 90_90 Main thread do work! Main thread do work! Main thread do work!
以上就是从0自学C#12--线程同步解决要领汇总以及优瑕玷的内容,更多相关内容请关注ki4网(www.ki4.cn)!