Home  >  Article  >  Backend Development  >  Self-study C#12 from 0--A summary of thread synchronization solutions and their advantages and disadvantages

Self-study C#12 from 0--A summary of thread synchronization solutions and their advantages and disadvantages

黄舟
黄舟Original
2017-02-04 11:11:151444browse

First of all, one thing is certain: Microsoft's Framework Class Library (FCL) ensures that all static methods are thread-safe.

FCL does not guarantee that instance methods are thread-safe. Because if all locks are added, it will cause a huge loss of performance. In addition, if each instance method needs to acquire and release a lock, in fact, you will end up with only one thread running in your application at any given moment, which has an obvious impact on performance.

The following introduces the primitive thread synchronization construct.

Primitive: refers to the simplest construct that can be used in code. There are two primitive constructs: user-mode and kernel-mode.

User mode

Special CPU instructions are used to coordinate threads.

Technology: volatile keyword, Interlocked class (interlock), spinlock (spin lock)

Common lock ①: volatile keyword indicates that a field can be modified by multiple threads executing simultaneously . Fields declared volatile are not subject to compiler optimizations (assuming access by a single thread). This ensures that the field presents the latest value at any time.

Interlocked class: Provides atomic operations for variables shared by multiple threads. . The so-called atomic operation refers to an operation that will not be interrupted by the thread scheduling mechanism; once this operation starts, it will run until the end without any context switch (switch to another thread) in the middle.

Common lock ②: The SpinLock structure is a low-level mutex synchronization primitive that rotates while waiting to acquire the lock. On multi-core computers, SpinLock will perform better than other lock types when wait times are expected to be short and contention conditions are rare. Even if SpinLock does not acquire the lock, it generates the thread's time slice. It does this to avoid thread priority inversion and to enable the garbage collector to continue executing. When using SpinLock, make sure that no thread holds the lock for more than a very short period of time, and that no thread blocks while holding the lock.

Advantages:

Primitive user-mode constructs should be used whenever possible, as they are significantly faster than kernel-mode constructs.

Coordination of threads occurs in hardware (that's why it's so fast).

But the Microsoft Windows operating system never detects that a thread is blocked on a primitive user-mode construct.

Because a thread pool blocking on a user-mode primitive construct is never considered blocked, the thread pool will not create a new thread to replace such a temporary thread.

These CPU instructions only block the thread for a relatively short time.

Disadvantages:

Only the Windows operating system kernel can stop a thread from running (preventing it from wasting CPU time).

Threads running in user mode may be preempted by the system, but the threads will be scheduled again as quickly as possible.

Threads that want to obtain resources but cannot obtain them temporarily will continue to "spin" in user mode, which may waste a lot of CPU time. Threads are always running on one CPU, which we call "livelock".

Example:

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.}

Kernel mode

Provided by the Windows operating system itself. They require calling functions implemented by the operating system kernel in the application's thread.

Technology: EventWaitHandle (event), Semaphore (semaphore), Mutex (mutex)

System.Object  
System.MarshalByRefObject    
System.Threading.WaitHandle      
System.Threading.EventWaitHandle      
System.Threading.Mutex      
System.Threading.Semaphore

Common lock ③: The Mutex class is a wrapper of the Win32 construct, which can cross application domains Boundaries are marshaled, can be used for multiple waits, and can be used to synchronize threads in different processes.

Advantages:

When a thread acquires resources owned by other threads through kernel-mode constructs, Windows blocks the thread to prevent it from wasting CPU time. When the resource becomes available, Windows resumes the thread, allowing it to access the resource. It does not occupy a CPU "spin".

Allows native and managed threads to be synchronized with each other.

Can synchronize threads running in different processes on the same machine.

Security settings can be applied to prevent unauthorized accounts from accessing them.

The thread can block until all kernel-mode constructs it is cooperating with are available, or until any kernel-mode construct in the set is available.

Threads blocked on kernel mode constructs can specify a timeout value: if the desired resource cannot be accessed within the specified time, the thread can be unblocked and perform other tasks.

Disadvantages:

Switching a thread from user mode to kernel mode (or vice versa) incurs a huge performance penalty, which is why kernel constructs should be avoided. In addition, if the thread is blocked all the time, it will lead to "deadlock" (deadlock).

Deadlock is always due to livelock, because livelock wastes CPU time and wastes memory (thread stack, etc.), while deadlock only wastes memory.

Mixed construction

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

技术: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--线程同步解决方法汇总以及优缺点的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn