• 技术文章 >后端开发 >C#.Net教程

    从0自学C#12--线程同步解决方法汇总以及优缺点

    黄舟黄舟2017-02-04 11:11:15原创541
    首先,肯定的一点:Microsoft的Framework Class Library(FCL)保证了所有静态方法都是线程安全的。

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

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    上一篇:从0自学C#11--多线程创建方法汇总以及优缺点 下一篇: 从0自学C#13--子类和父类方法的锁对象问题

    相关文章推荐

    • c语言中形参的缺省存储类别是什么• C# 2.0 Specification (泛型三)• C#的多线程机制初探(3)• C++中内存泄漏的检测• .NET原型模式讲解

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网