[转]C#编程总结(三)线程同步

本文转自:http://www.cnblogs.com/yank/p/3227324.html

当应用程序中采用多只线程的一个功利是每个线程都可异步执行。对于 Windows
应用程序,耗时之天职可以当后台执行,而使应用程序窗口和控件保持响应。对于服务器应用程序,多线程处理提供了为此不同线程处理每个传入请求的力。否则,在完全满足前一个告之前,将无法处理每个新请求。然而,线程的异步特性意味着要协调对资源(如文件句柄、网络连接和内存)的走访。否则,两单或重多之线程可能当同一时间访问同的资源,而每个线程都未知情其他线程的操作。

“如果觉得可行,请帮顶!
如果生不足之处,欢迎拍砖!”

线程同步的方法

     线程同步有:临界区、互斥区、事件、信号量四种植方法
  临界区(Critical
Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的分别
  1、临界区:通过对多线程的失误行化来走访公共资源还是同等截代码,速度快,适合控制数据访问。在随机时刻才允许一个线程对共享资源进行走访,如果发差不多单线程试图访问公共资源,那么在产生一个线程进入后,其他试图访问公共资源的线程将受吊起于,并直接相当及进入临界区的线程离开,临界区于叫假释后,其他线程才可抢占。
  2、互斥量:采用互斥对象机制。
只有所有互斥对象的线程才产生看公共资源的权限,因为互斥对象就发生一个,所以能够担保集体资源不会见又吃多单线程访问。互斥不仅会兑现同应用程序的公物资源安全共享,还能促成不同应用程序的共用资源安全共同享
  3、信号量:它同意多单线程在同时刻访问同一资源,但是得限制以同一时刻访问这个资源的无限要命线程数目
  4、事 件:
通过通告操作的措施来维系线程的共同,还得好实现对多个线程的优先级比较的操作

C#遭受经常表现线程同步方法

咱介绍几栽常用之C#展开线程同步的主意,这些措施得以依据其规律,找到呼应上面的季栽档次之一。

1、Interlocked 为多单线程共享的变量提供原子操作。

冲涉,那些急需以差不多线程情况下被保护的资源通常是整型值,且这些整型值在多线程下最普遍的操作就是与日俱增、递减或相加操作。Interlocked类提供了一个专程的建制用来完成这些特定的操作。这个类似提供了Increment、Decrement、Add静态方法用于对int或long型变量的递增、递减或相加操作。此类的道可以防范或当下列情况来的错误:计划程序于有线程正在更新可由于其他线程访问的变量时切换上下文;或者当半独线程在不同的处理器上面世执行时。
此类的分子不吸引那个。

Increment 和 Decrement 方法递增或递减变量并拿结果值存储在么操作中。
在大部分计算机达,增加变量操作不是一个原子操作,需要实施下列步骤:

1)将实例变量中之价加载到寄存器中。 2)增加还是调减该值。
3)在实例变量中存储该值。 如果无使用 Increment 和
Decrement,线程会以尽了前少单步骤后给抢。
然后由其余一个线程执行有三独步骤。
当第一独线程重新开推行时,它挂实例变量中的价值,造成第二只线程执行增减操作的结果丢失。 

Exchange 方法自动交换指定变量的值。 CompareExchange
方法结合了有限独操作:比较简单个价与基于比较的结果将第三单价值存储于内部一个变量中。
比较和交换操作以原子操作实践。

案例解析:共享打印机。

寻常我们见面采用共享打印机,几令微机共享一令打印机,每台计算机可以出打印指令,可能会见油然而生并发情况。当然我们清楚,打印机采用了排技术。为了简化操作,我们而,在打印机收到命令时,即可打印,而且每当同一时间只能发出一个打印任务在实施。我们运用Interlocked方法来兑现多线程同步。具体代码如下:

图片 1😉

using System;
using System.Threading;

namespace MutiThreadSample.ThreadSynchronization
{
    class PrinterWithInterlockTest
    {
        /// <summary>
        /// 正在使用的打印机
        /// 0代表未使用,1代表正在使用
        /// </summary>
        public static int UsingPrinter = 0;
        /// <summary>
        /// 计算机数量
        /// </summary>
        public static readonly int ComputerCount = 3;
        /// <summary>
        /// 测试
        /// </summary>
        public static void TestPrint()
        {
            Thread thread;
            Random random = new Random();
            for (int i = 0; i < ComputerCount; i++)
            {
                thread = new Thread(MyThreadProc);
                thread.Name = string.Format("Thread{0}",i);
                Thread.Sleep(random.Next(3));
                thread.Start();
            }
        }
        /// <summary>
        /// 线程执行操作
        /// </summary>
        private static void MyThreadProc()
        {
            //使用打印机进行打印
            UsePrinter();
            //当前线程等待1秒
            Thread.Sleep(1000);
        }
        /// <summary>
        /// 使用打印机进行打印
        /// </summary>
        private static bool UsePrinter()
        {
            //检查大引进是否在使用,如果原始值为0,则为未使用,可以进行打印,否则不能打印,继续等待
            if (0 == Interlocked.Exchange(ref UsingPrinter, 1))
            {
                Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);

                //Code to access a resource that is not thread safe would go here.

                //Simulate some work
                Thread.Sleep(500);

                Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);

                //释放打印机
                Interlocked.Exchange(ref UsingPrinter, 0);
                return true;
            }
            else
            {
                Console.WriteLine("   {0} was denied the lock", Thread.CurrentThread.Name);
                return false;
            }
        }

    }
}

图片 2😉

2、lock 关键字

lock
关键字用报告句片标记为临界区,方法是取给定对象的互斥锁,执行语句,然后释放该锁。
lock
确保当一个线程位于代码的临界区时不时,另一个线程不进入临界区。如果另外线程试图跻身锁定的代码,则它以直接等候(即于阻),直到该对象吃放走。

图片 3😉

public void Function()
{
      System.Object locker= new System.Object();
      lock(locker)
     {
           // Access thread-sensitive resources.
     }
}    

图片 4😉

lock 调用块开始位置的 Enter
和片了位置的 Exit。

供于 lock
关键字的参数必须为根据引用类型的对象,该对象用来定义锁的限。在上例中,锁之克界定为这个函数,因为函数外不有任何对拖欠目标的援。严格地游说,提供给
lock
的对象只是用来唯一地标识由多单线程共享的资源,所以她可是任意类实例。然而,实际上,此目标通常表示用开展线程同步的资源。例如,如果一个器皿对象将让多只线程使用,则好拿该容器传递让
lock,而 lock
后面的共代码块用访问该容器。只要任何线程在访该容器前先锁定该容器,则指向拖欠目标的顾将是安全共同的。通常,最好避免锁定
public
类型或锁定不叫应用程序控制的对象实例,例如,如果该实例可以吃公开访问,则
lock(this)
可能会见生出问题,因为不给控制的代码也恐怕会见锁定该目标。这或许造成死锁,即有限单或再多单线程等待释放同一对象。出于同样的原故,锁定公共数据类型(相比叫对象)也说不定造成问题。锁定字符串尤其危险,因为字符串被公语言运行库
(CLR)“暂留”。这意味所有程序中其他给定字符串都止来一个实例,就是随即和一个靶表示了颇具运行的应用程序域的装有线程中的该文件。因此,只要以应用程序进程遭到之其它位置处于有相同内容的字符串上放置了锁,就拿锁定应用程序中该字符串的所有实例。因此,最好锁定不见面被暂留的私或被保障成员。某些类提供特别用于锁定的成员。例如,Array
类型提供 SyncRoot。许多集结类型也提供 SyncRoot。

科普的组织 lock (this)、lock (typeof (MyType)) 和 lock (“myLock”)
违反这个则:

1)如果实例可以让公访问,将应运而生 lock (this) 问题。 2)如果 MyType
可以吃公访问,将现出 lock (typeof (MyType)) 问题。
3)由于经过被利用同一字符串的别其它代码用联手享同一个吊,所以出现
lock(“myLock”) 问题。 最佳做法是概念 private 对象来锁定, 或 private
static 对象变量来保安有着实例所共有的多寡。关于锁之研讨,大家可以参见:

http://www.cnblogs.com/yank/archive/2008/10/28/1321119.html

案例剖析:继续应用共享打印机的案例

咱们唯有需要对眼前的例子稍作改即可实现lock进行共同。

声称锁对象:

        /// <summary>
        /// 正在使用的打印机
        /// </summary>
        private static object UsingPrinterLocker = new object();

拿打印方式修改如下:

图片 5😉

        /// <summary>
        /// 使用打印机进行打印
        /// </summary>
        private static void UsePrinter()
        {
            //临界区
            lock (UsingPrinterLocker)
            {
                Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
                //模拟打印操作
                Thread.Sleep(500);
                Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
            }
        }

图片 6😉

3、监视器

跟 lock 关键字类似,监视器防止多个线程同时执行代码块。Enter
方法允许一个都仅一个线程继续执行后面的话语;其他兼具线程都拿被挡,直到执行语句的线程调用
Exit。这和用 lock 关键字一样。事实上,lock 关键字就是用 Monitor
类来落实的。例如:(继续修改共享打印机案例,增加方法UsePrinterWithMonitor)

图片 7😉

        /// <summary>
        /// 使用打印机进行打印
        /// </summary>
        private static void UsePrinterWithMonitor()
        {
            System.Threading.Monitor.Enter(UsingPrinterLocker);
            try
            {
                Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
                //模拟打印操作
                Thread.Sleep(500);
                Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
            }
            finally
            {
                System.Threading.Monitor.Exit(UsingPrinterLocker);
            }
        }

图片 8😉

 使用 lock 关键字便比一直利用 Monitor 类更可取,一方面是因 lock
更简短,另一方面是为 lock
确保了就算让保障之代码引发那个,也足以释放基础监视器。这是由此 finally
关键字来促成之,无论是否抓住那个它还执行关联的代码块。

4、同步事件和等候句柄

     
使用锁或者监视器对于防止以推行区分线程的代码块大有因此,但是这些构造不允许一个线程向其它一个线程传达事件。这得“同步事件”,它是发出个别个状态(终止与非终止)的对象,可以据此来激活和挂于线程。让线程等待非终止的一块事件可以将线程挂于,将事件状态更改为平息可以用线程激活。如果线程试图等待都停止的波,则线程将继续执行,而非会见延迟。 
   

      同步事件时有发生零星栽:AutoResetEvent 和
ManualResetEvent。它们中唯一的两样在,无论何时,只要 AutoResetEvent
激活线程,它的状态将自行从停变为非终止。相反,ManualResetEvent
允许她的终止状态激活任意多只线程,只有当她的 Reset
方法为调用时才还原暨非终止状态。

     等待句柄,可以经调用一种植等待方法,如 WaitOne、WaitAny 或
WaitAll,让线程等待事件。System.Threading.WaitHandle.WaitOne
使线程一直等候,直到单个事件成终止状态;System.Threading.WaitHandle.WaitAny
阻止线程,直到一个或多单指令的事件变成终止状态;System.Threading.WaitHandle.WaitAll
阻止线程,直到所有指示的波还改为终止状态。当调用事件之 Set
方法时,事件将改为终止状态。

     AutoResetEvent 允许线程通过发信号互相通信。
通常,当线程需要把访问资源时用该类。线程通过调用 AutoResetEvent 上之
WaitOne 来等待信号。 如果 AutoResetEvent
为非终止状态,则线程会为挡,并等候眼前决定资源的线程通过调用 Set
来通知资源可用。调用 Set 向 AutoResetEvent 发信号为释放等待线程。
AutoResetEvent
将保障住状态,直到一个正在守候的线程被保释,然后自动回到非终止状态。
如果没外线程在待,则状态将无限期地涵养也已状态。如果当
AutoResetEvent 为平息状态时线程调用 WaitOne,则线程不见面让截留。
AutoResetEvent 将马上放飞线程并回到非终止状态。
可以透过将一个布尔值传递给构造函数来支配 AutoResetEvent
的初始状态:如果开状态也平息状态,则为 true;否则也 false。
AutoResetEvent 也得同 staticWaitAll 和 WaitAny 方法齐以。
案例:

案例介绍: 

今我们来做饭,做饭呢,需要一致菜、一稀饭。今天我们吃鱼。

受粥和做鱼,是比较复杂的做事流程, 做粥:选材、淘米、熬制
做鱼:洗鱼、切鱼、腌制、烹调
为了提高效率,我们就此单薄独线程来准备就顿饭,但是,现在只是出一致人数锅,只能当一个召开截止后,另一个才会拓展末段之烹饪。

来拘禁实例代码:

图片 9😉

using System;
using System.Threading;

namespace MutiThreadSample.ThreadSynchronization
{
    /// <summary>
    /// 案例:做饭
    /// 今天的Dinner准备吃鱼,还要熬粥
    /// 熬粥和做鱼,是比较复杂的工作流程,
    /// 做粥:选材、淘米、熬制
    /// 做鱼:洗鱼、切鱼、腌制、烹调
    /// 我们用两个线程来准备这顿饭
    /// 但是,现在只有一口锅,只能等一个做完之后,另一个才能进行最后的烹调
    /// </summary>
    class CookResetEvent
    {
        /// <summary>
        /// 
        /// </summary>
        private AutoResetEvent resetEvent = new AutoResetEvent(false);
        /// <summary>
        /// 做饭
        /// </summary>
        public void Cook()
        {
            Thread porridgeThread = new Thread(new ThreadStart(Porridge));
            porridgeThread.Name = "Porridge";
            porridgeThread.Start();

            Thread makeFishThread = new Thread(new ThreadStart(MakeFish));
            makeFishThread.Name = "MakeFish";
            makeFishThread.Start();

            //等待5秒
            Thread.Sleep(5000);

            resetEvent.Reset();
        }
        /// <summary>
        /// 熬粥
        /// </summary>
        public void Porridge()
        { 
            //选材
            Console.WriteLine("Thread:{0},开始选材", Thread.CurrentThread.Name);

            //淘米
            Console.WriteLine("Thread:{0},开始淘米", Thread.CurrentThread.Name);

            //熬制
            Console.WriteLine("Thread:{0},开始熬制,需要2秒钟", Thread.CurrentThread.Name);
            //需要2秒钟
            Thread.Sleep(2000);
            Console.WriteLine("Thread:{0},粥已经做好,锅闲了", Thread.CurrentThread.Name);

            resetEvent.Set();
        }
        /// <summary>
        /// 做鱼
        /// </summary>
        public void MakeFish()
        { 
            //洗鱼
            Console.WriteLine("Thread:{0},开始洗鱼",Thread.CurrentThread.Name);

            //腌制
            Console.WriteLine("Thread:{0},开始腌制", Thread.CurrentThread.Name);

            //等待锅空闲出来
            resetEvent.WaitOne();

            //烹调
            Console.WriteLine("Thread:{0},终于有锅了", Thread.CurrentThread.Name);
            Console.WriteLine("Thread:{0},开始做鱼,需要5秒钟", Thread.CurrentThread.Name);
            Thread.Sleep(5000);
            Console.WriteLine("Thread:{0},鱼做好了,好香", Thread.CurrentThread.Name);

            resetEvent.Set();
        }
    }
}

图片 10😉

 ManualResetEvent和AutoResetEvent用法基本相仿,这里不多做牵线。

5、Mutex对象

      mutex
与监视器类似;它防止多只线程在某一时间同时实施某代码块。事实上,名称“mutex”是术语“互相排斥
(mutually exclusive)”的简写形式。然而跟监视器不同之是,mutex
可以据此来而跨越进程的线程同步。mutex 由 Mutex
类表示。当用于进程之中一块时,mutex 称为“命名
mutex”,因为她用用来其它一个应用程序,因此她不可知透过全局变量或静态变量共享。必须叫它们指定一个称呼,才能够如少数单应用程序访问与一个
mutex 对象。       尽管 mutex 可以用于进程内的线程同步,但是利用 Monitor
通常越可取,因为监视器是特意为 .NET Framework
而计划之,因而它可以更好地应用资源。相比之下,Mutex 类是 Win32
构造的卷入。尽管 mutex 比监视器更为强大,但是相对于 Monitor
类,它所要之互操作转换更耗费计算资源。

      本地 mutex 和系统 mutex       Mutex 分点儿栽类型:本地 mutex
和命名系统 mutex。 如果以接受名称的构造函数创建了 Mutex
对象,那么该目标将跟有着该名的操作系统对象相关联。 命名的系 mutex
在总体操作系统中还可见,并且只是用以共同进程走。 您可以创造多单 Mutex
对象来表示一致命名系统 mutex,而且若可行使 OpenExisting
方法打开现有的命名系统 mutex。       本地 mutex 仅设有让经过中。
进程被援本地 Mutex 对象的任意线程都得使当地 mutex。 每个 Mutex
对象还是一个独门的地头 mutex。

以地头Mutex中,用法与Monitor基本一致

承修改前的打印机案例:

声明Mutex对象:

        /// <summary>
        /// mutex对象
        /// </summary>
        private static Mutex mutex = new Mutex();

具体操作:

图片 11😉

        /// <summary>
        /// 使用打印机进行打印
        /// </summary>
        private static void UsePrinterWithMutex()
        {
            mutex.WaitOne();
            try
            {
                Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
                //模拟打印操作
                Thread.Sleep(500);
                Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
            }
            finally
            {
                mutex.ReleaseMutex();
            }
        }

图片 12😉

多线程调用:

图片 13😉

        /// <summary>
        /// 测试
        /// </summary>
        public static void TestPrint()
        {
            Thread thread;
            Random random = new Random();
            for (int i = 0; i < ComputerCount; i++)
            {
                thread = new Thread(MyThreadProc);
                thread.Name = string.Format("Thread{0}", i);
                Thread.Sleep(random.Next(3));
                thread.Start();
            }
        }
        /// <summary>
        /// 线程执行操作
        /// </summary>
        private static void MyThreadProc()
        {
            //使用打印机进行打印
            //UsePrinter();
            //monitor同步
            //UsePrinterWithMonitor();
            //用Mutex同步
            UsePrinterWithMutex();
            //当前线程等待1秒
            Thread.Sleep(1000);
        }

图片 14😉

末尾的打印机案例代码:

图片 15图片 16

图片 17😉

using System;
using System.Threading;

namespace MutiThreadSample.ThreadSynchronization
{
    class PrinterWithLockTest
    {
        /// <summary>
        /// 正在使用的打印机
        /// </summary>
        private static object UsingPrinterLocker = new object();
        /// <summary>
        /// 计算机数量
        /// </summary>
        public static readonly int ComputerCount = 3;
        /// <summary>
        /// mutex对象
        /// </summary>
        private static Mutex mutex = new Mutex();
        /// <summary>
        /// 测试
        /// </summary>
        public static void TestPrint()
        {
            Thread thread;
            Random random = new Random();
            for (int i = 0; i < ComputerCount; i++)
            {
                thread = new Thread(MyThreadProc);
                thread.Name = string.Format("Thread{0}", i);
                Thread.Sleep(random.Next(3));
                thread.Start();
            }
        }
        /// <summary>
        /// 线程执行操作
        /// </summary>
        private static void MyThreadProc()
        {
            //使用打印机进行打印
            //UsePrinter();
            //monitor同步
            //UsePrinterWithMonitor();
            //用Mutex同步
            UsePrinterWithMutex();
            //当前线程等待1秒
            Thread.Sleep(1000);
        }
        /// <summary>
        /// 使用打印机进行打印
        /// </summary>
        private static void UsePrinter()
        {
            //临界区
            lock (UsingPrinterLocker)
            {
                Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
                //模拟打印操作
                Thread.Sleep(500);
                Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
            }
        }

        /// <summary>
        /// 使用打印机进行打印
        /// </summary>
        private static void UsePrinterWithMonitor()
        {
            System.Threading.Monitor.Enter(UsingPrinterLocker);
            try
            {
                Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
                //模拟打印操作
                Thread.Sleep(500);
                Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
            }
            finally
            {
                System.Threading.Monitor.Exit(UsingPrinterLocker);
            }
        }

        /// <summary>
        /// 使用打印机进行打印
        /// </summary>
        private static void UsePrinterWithMutex()
        {
            mutex.WaitOne();
            try
            {
                Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
                //模拟打印操作
                Thread.Sleep(500);
                Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
            }
            finally
            {
                mutex.ReleaseMutex();
            }
        }
    }
}

图片 18😉

View Code

 6、读取器/编写器锁

      ReaderWriterLockSlim
类允许多只线程同时读取一个资源,但每当向阳该资源写副常要求线程等待以博取独占锁。

      可以当应用程序中动用
ReaderWriterLockSlim,以便在顾一个共享资源的线程之间提供协调共同。
获得的缉是本着 ReaderWriterLockSlim 本身的。      
设计而应用程序的组织,让读取和写入操作的日尽可能最缺少。
因为写副锁是排遣他的,所以长时之写入操作会直接影响吞吐量。
长时间之读取操作会阻止处于等候状态的编写器,并且,如果至少发生一个线程在等候写入访问,则呼吁读取访问的线程也拿让拦截。

案例:构造一个线程安全的缓存

图片 19😉

using System;
using System.Threading;
using System.Collections.Generic;


namespace MutiThreadSample.ThreadSynchronization
{
    /// <summary>
    /// 同步Cache
    /// </summary>
    public class SynchronizedCache
    {
        private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
        private Dictionary<int, string> innerCache = new Dictionary<int, string>();
        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string Read(int key)
        {
            cacheLock.EnterReadLock();
            try
            {
                return innerCache[key];
            }
            finally
            {
                cacheLock.ExitReadLock();
            }
        }
        /// <summary>
        /// 添加项
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public void Add(int key, string value)
        {
            cacheLock.EnterWriteLock();
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
        }
        /// <summary>
        /// 添加项,有超时限制
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public bool AddWithTimeout(int key, string value, int timeout)
        {
            if (cacheLock.TryEnterWriteLock(timeout))
            {
                try
                {
                    innerCache.Add(key, value);
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 添加或者更新
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public AddOrUpdateStatus AddOrUpdate(int key, string value)
        {
            cacheLock.EnterUpgradeableReadLock();
            try
            {
                string result = null;
                if (innerCache.TryGetValue(key, out result))
                {
                    if (result == value)
                    {
                        return AddOrUpdateStatus.Unchanged;
                    }
                    else
                    {
                        cacheLock.EnterWriteLock();
                        try
                        {
                            innerCache[key] = value;
                        }
                        finally
                        {
                            cacheLock.ExitWriteLock();
                        }
                        return AddOrUpdateStatus.Updated;
                    }
                }
                else
                {
                    cacheLock.EnterWriteLock();
                    try
                    {
                        innerCache.Add(key, value);
                    }
                    finally
                    {
                        cacheLock.ExitWriteLock();
                    }
                    return AddOrUpdateStatus.Added;
                }
            }
            finally
            {
                cacheLock.ExitUpgradeableReadLock();
            }
        }
        /// <summary>
        /// 删除项
        /// </summary>
        /// <param name="key"></param>
        public void Delete(int key)
        {
            cacheLock.EnterWriteLock();
            try
            {
                innerCache.Remove(key);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public enum AddOrUpdateStatus
        {
            Added,
            Updated,
            Unchanged
        };
    }
}

图片 20😉

 7、Semaphore 和 SemaphoreSlim

      System.Threading.Semaphore
类表示一个命名(系统范围)信号量或地面信号量。 它是一个针对 Win32
信号量对象的简包装。 Win32
信号量是计数信号量,可用于控制对资源池的顾。       SemaphoreSlim
类表示一个轻量的飞快信号量,可用以在一个预测等待时会异常差的长河内开展等待。
SemaphoreSlim 会尽可能多地指由国有语言运行时 (CLR) 提供的一块基元。
但是,它吧会基于需要提供延迟初始化的、基于内核的等句柄,以支撑待多个信号量。
SemaphoreSlim
还支持使用取消标记,但她不支持命名信号量或采用等句柄来开展同步。

      线程通过调用 WaitOne 方法来上信号量,此方是于 WaitHandle
类派生的。 当调用返回时,信号量的计数将压缩。
当一个线程请求项而计数为零时,该线程会被阻碍。 当线程通过调用 Release
方法释放信号量时,将同意让拦的线程进入。
并无包让死的线程进入信号量的次第,例如先进先出 (FIFO) 或后进先出
(LIFO)。信号量的计数在历次线程进入信号量时减多少,在线程释放信号量时增加。
当计数为零时,后面的求将为打断,直到来另外线程释放信号量。
当所有的线程都早就放出信号量时,计数达到创建信号量时所指定的卓绝酷价值。

案例分析:购买火车票

还得排队进行打,购买窗口是片的,只发生窗口空闲时才能够购买 

图片 21图片 22

using System;
using System.Threading;

namespace MutiThreadSample.ThreadSynchronization
{
    /// <summary>
    /// 案例:支付流程
    /// 如超市、药店、火车票等,都有限定的几个窗口进行结算,只有有窗口空闲,才能进行结算。
    /// 我们就用多线程来模拟结算过程
    /// </summary>
    class PaymentWithSemaphore
    {
        /// <summary>
        /// 声明收银员总数为3个,但是当前空闲的个数为0,可能还没开始上班。
        /// </summary>
        private static Semaphore IdleCashiers = new Semaphore(0, 3);
        /// <summary>
        /// 测试支付过程
        /// </summary>
        public static void TestPay()
        {
            ParameterizedThreadStart start = new ParameterizedThreadStart(Pay);
            //假设同时有5个人来买票
            for (int i = 0; i < 5; i++)
            {
                Thread thread = new Thread(start);
                thread.Start(i);
            }

            //主线程等待,让所有的的线程都激活
            Thread.Sleep(1000);
            //释放信号量,2个收银员开始上班了或者有两个空闲出来了
            IdleCashiers.Release(2);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        public static void Pay(object obj)
        {
            Console.WriteLine("Thread {0} begins and waits for the semaphore.", obj);
            IdleCashiers.WaitOne();
            Console.WriteLine("Thread {0} starts to Pay.",obj);
            //结算
            Thread.Sleep(2000);
            Console.WriteLine("Thread {0}: The payment has been finished.",obj);

            Console.WriteLine("Thread {0}: Release the semaphore.", obj);
            IdleCashiers.Release();
        }
    }
}

采购火车票

 8、障碍(Barrier)4.0后技术

一旦多独任务会运用互动方式基于某种算法在多单等级受到协同工作。
通过当平等层层号间活动来协作完成同样组任务,此时该组中之每个任务发信号指出其已经到达指定阶段的
Barrier 并且暗中守候其他任务到。 相同的 Barrier 可用于多独阶段。

9、SpinLock(4.0后)    
 SpinLock结构是一个低级别的排斥同步基元,它以伺机取锁时进行盘。
在多按处理器达,当等时预计较短且极其少出现什么样用状态经常,SpinLock
的性质将超过其他类别的沿。 不过,我们建议乃就以经分析确定
System.Threading.Monitor 方法或者 Interlocked
方法明显下降了先后的性能时采取 SpinLock。       即使 SpinLock
未沾锁,它吧会见出线程的时间片。
它这样做是为着避免线程优先级别反转,并要垃圾回收器能够继续执行。 在应用
SpinLock
时,请保管其他线程持有锁的时刻未会见过一个怪少的工夫段,并保管其他线程在享有锁时不会见堵塞。
      由于 SpinLock
是一个值类型,因此,如果您想两个副本都引用和一个吊,则须经过引用显式传递该锁。

图片 23图片 24

using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MutiThreadSample.ThreadSynchronization
{
    class SpinLockSample
    {
        public static void Test()
        {
            SpinLock sLock = new SpinLock();
            StringBuilder sb = new StringBuilder();
            Action action = () =>
            {
                bool gotLock = false;
                for (int i = 0; i < 100; i++)
                {
                    gotLock = false;
                    try
                    {
                        sLock.Enter(ref gotLock);
                        sb.Append(i.ToString());
                    }
                    finally
                    {
                        //真正获取之后,才释放
                        if (gotLock) sLock.Exit();
                    }
                }
            };

            //多线程调用action
            Parallel.Invoke(action, action, action);
            Console.WriteLine("输出:{0}",sb.ToString());
        }
    }
}

View Code

10、SpinWait(4.0后)

      System.Threading.SpinWait
是一个轻量同步类型,可以在没有级别方案遭应用她来避免内核事件所欲的强开的上下文切换和基础转换。
在多对处理器上,当预测资源不会见保留老丰富一段时间时,如果叫等待线程以用户模式旋转数十要么数百单周期,然后还尝试取资源,则效率会重胜。
如果当转后资源变为可用之,则可省去数千单周期。
如果资源仍然未可用,则单独花了少量周期,并且还可以展开基于内核的等。
这无异现转-等待的做有时称“两阶段等操作”。

下面的主导示例采用微软案例:无锁堆栈

图片 25图片 26

using System;
using System.Threading;

namespace MutiThreadSample.ThreadSynchronization
{
    public class LockFreeStack<T>
    {
        private volatile Node m_head;

        private class Node { public Node Next; public T Value; }

        public void Push(T item)
        {
            var spin = new SpinWait();
            Node node = new Node { Value = item }, head;
            while (true)
            {
                head = m_head;
                node.Next = head;
                if (Interlocked.CompareExchange(ref m_head, node, head) == head) break;
                spin.SpinOnce();
            }
        }

        public bool TryPop(out T result)
        {
            result = default(T);
            var spin = new SpinWait();

            Node head;
            while (true)
            {
                head = m_head;
                if (head == null) return false;
                if (Interlocked.CompareExchange(ref m_head, head.Next, head) == head)
                {
                    result = head.Value;
                    return true;
                }
                spin.SpinOnce();
            }
        }
    }
}

View Code

 总结:

尽管发生诸如此类多之技艺,但是差的艺对许不同之场面,我们须熟悉那个特点及适用范围。在采用时,必须切实问题具体分析,选择最佳的同步方式。

 

 

分类: 差不多线程技术

标签:
线程同步

 

相关文章