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

首先,肯定的一点: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[] tasks = new Task[10];            Stopwatch watch = new Stopwatch();            object syncRoot = new Object();            watch.Start();            for (int i = 0; i < tasks.Length; i++)            {                tasks[i] = Task.Factory.StartNew(() =>(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_96Tick ID is 99_99Tick ID is 97_97Tick ID is 98_98Tick ID is 95_95Tick ID is 94_94Tick ID is 93_93Tick ID is 92_92Tick ID is 91_91Tick ID is 90_90Main thread do work!Main thread do work!Main thread do work!

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1432043.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月17日 06:09:32
下一篇 2025年12月17日 06:09:45

相关推荐

发表回复

登录后才能评论
关注微信