Selbststudium C#12 ab 0 – Eine Zusammenfassung der Thread-Synchronisierungslösungen und ihrer Vor- und Nachteile

黄舟
Freigeben: 2017-02-04 11:11:15
Original
1461 Leute haben es durchsucht

Zunächst einmal steht fest: Die Framework Class Library (FCL) von Microsoft sorgt dafür, dass alle statischen Methoden threadsicher sind.

FCL garantiert nicht, dass Instanzmethoden threadsicher sind. Denn wenn alle Sperren hinzugefügt werden, führt dies zu einem enormen Leistungsverlust. Wenn außerdem jede Instanzmethode eine Sperre erwerben und freigeben muss, wird in Ihrer Anwendung zu jedem Zeitpunkt tatsächlich nur ein Thread ausgeführt, was offensichtliche Auswirkungen auf die Leistung hat.

Im Folgenden wird das primitive Thread-Synchronisationskonstrukt vorgestellt.

Primitive: beziehen sich auf die einfachsten Konstrukte, die im Code verwendet werden können. Es gibt zwei primitive Konstrukte: Benutzermodus und Kernelmodus.

Benutzermodus

verwendet spezielle CPU-Anweisungen, um Threads zu koordinieren.

Technologie: flüchtiges Schlüsselwort, Interlocked-Klasse (Interlock), Spinlock (Spin-Lock)

Gemeinsame Sperre ①: flüchtiges Schlüsselwort gibt an, dass ein Feld von mehreren gleichzeitig ausgeführten Threads geändert werden kann. Felder, die als flüchtig deklariert sind, unterliegen keinen Compiler-Optimierungen (vorausgesetzt, der Zugriff erfolgt über einen einzelnen Thread). Dadurch wird sichergestellt, dass das Feld jederzeit den neuesten Wert anzeigt.

Verriegelte Klasse: Bietet atomare Operationen für Variablen, die von mehreren Threads gemeinsam genutzt werden. . Die sogenannte atomare Operation bezieht sich auf eine Operation, die nicht durch den Thread-Planungsmechanismus unterbrochen wird. Sobald diese Operation gestartet ist, wird sie bis zum Ende ohne Kontextwechsel (Wechsel zu einem anderen Thread) in der Mitte ausgeführt.

Gemeinsame Sperren ②: Die SpinLock-Struktur ist ein Mutex-Synchronisierungsprimitiv auf niedriger Ebene, das sich dreht, während es darauf wartet, eine Sperre zu erhalten. Auf Computern mit mehreren Kernen bietet SpinLock eine bessere Leistung als andere Sperrtypen, wenn kurze Wartezeiten zu erwarten sind und Konfliktbedingungen selten sind. Auch wenn SpinLock die Sperre nicht erhält, generiert es die Zeitscheibe des Threads. Dies geschieht, um eine Thread-Prioritätsumkehr zu vermeiden und dem Garbage Collector die Fortsetzung der Ausführung zu ermöglichen. Stellen Sie bei der Verwendung von SpinLock sicher, dass kein Thread die Sperre länger als einen sehr kurzen Zeitraum hält und dass kein Thread blockiert, während er die Sperre hält.

Vorteile:

Primitive Benutzermodus-Konstrukte sollten nach Möglichkeit verwendet werden, da sie deutlich schneller sind als Kernel-Modus-Konstrukte.

Die Koordination der Threads erfolgt in Hardware (deshalb ist sie so schnell).

Aber das Microsoft Windows-Betriebssystem erkennt nie, dass ein Thread auf einem primitiven Benutzermoduskonstrukt blockiert ist.

Da die Blockierung eines Thread-Pools auf einem primitiven Konstrukt im Benutzermodus niemals als blockiert gilt, erstellt der Thread-Pool keinen neuen Thread, um einen solchen temporären Thread zu ersetzen.

Diese CPU-Anweisungen blockieren den Thread nur für einen relativ kurzen Zeitraum.

Nachteile:

Nur ​​der Kernel des Windows-Betriebssystems kann die Ausführung eines Threads stoppen (und so verhindern, dass er CPU-Zeit verschwendet).

Threads, die im Benutzermodus ausgeführt werden, werden möglicherweise vom System vorbelegt, die Threads werden jedoch so schnell wie möglich erneut geplant.

Threads, die Ressourcen erhalten möchten, diese aber vorübergehend nicht erhalten können, „drehen“ sich weiterhin im Benutzermodus, was möglicherweise viel CPU-Zeit verschwendet. Threads laufen immer auf einer CPU, was wir „Livelock“ nennen.

Instanz:

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.}
Nach dem Login kopieren

Kernelmodus

Bereitgestellt vom Windows-Betriebssystem selbst. Sie erfordern den Aufruf von Funktionen, die vom Betriebssystemkernel im Anwendungsthread implementiert werden.

Technologie: EventWaitHandle (Ereignis), Semaphore (Semaphor), Mutex (Mutex)

System.Object  
System.MarshalByRefObject    
System.Threading.WaitHandle      
System.Threading.EventWaitHandle      
System.Threading.Mutex      
System.Threading.Semaphore
Nach dem Login kopieren

Gemeinsame Sperren ③: Die Mutex-Klasse ist ein Wrapper des Win32-Konstrukts, der verwendet werden kann Anwendungsübergreifend: Das Marshalling an Programmdomänengrenzen kann für mehrere Wartevorgänge verwendet werden und kann zum Synchronisieren von Threads in verschiedenen Prozessen verwendet werden.

Vorteile:

Wenn ein Thread über Kernelmodus-Konstrukte Ressourcen anderer Threads erwirbt, blockiert Windows den Thread, um zu verhindern, dass er CPU-Zeit verschwendet. Wenn die Ressource verfügbar wird, setzt Windows den Thread fort und ermöglicht ihm den Zugriff auf die Ressource. Es belegt keinen CPU-„Spin“.

Ermöglicht die Synchronisierung nativer und verwalteter Threads miteinander.

Threads, die in verschiedenen Prozessen auf demselben Computer ausgeführt werden, können synchronisiert werden.

Sicherheitseinstellungen können angewendet werden, um zu verhindern, dass nicht autorisierte Konten darauf zugreifen.

Ein Thread kann blockieren, bis alle Kernelmodus-Konstrukte, mit denen er zusammenarbeitet, verfügbar sind, oder bis ein beliebiges Kernelmodus-Konstrukt im Satz verfügbar ist.

Threads, die auf Kernel-Modus-Konstrukten blockiert sind, können einen Timeout-Wert angeben: Wenn auf die gewünschte Ressource nicht innerhalb der angegebenen Zeit zugegriffen werden kann, kann der Thread entsperrt werden und andere Aufgaben ausführen.

Nachteile:

Das Umschalten eines Threads vom Benutzermodus in den Kernelmodus (oder umgekehrt) führt zu einer enormen Leistungseinbuße, weshalb Kernelkonstrukte genau deshalb vermieden werden sollten. Wenn der Thread außerdem ständig blockiert ist, führt dies zu einem „Deadlock“ (Deadlock).

Deadlock ist immer auf Livelock zurückzuführen, da Livelock CPU-Zeit und Speicher (Thread-Stack usw.) verschwendet, während Deadlock nur Speicher verschwendet.

Hybridbauweise

兼具上面两者的长处。在没有竞争的情况下,快而且不会阻塞(就像用户模式)。在有竞争的情况,希望它被操作系统内核阻塞。

技术: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();
        }
Nach dem Login kopieren

输出:

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!
Nach dem Login kopieren

以上就是从0自学C#12--线程同步解决方法汇总以及优缺点的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage