【原创】细化架构设计之线程同步

细化架构设计之线程同步

 

 在现代的程序开发中,资源的同步是一个比较重要的课题,.Net,对这部分有很丰富类库供我们使用,现在总结一下在各种情况下对资源同步的机制。
1volatile

1.1 说明

volatile是最简单的一种同步方法,当然简单是要付出代价的。它只能在变量一级做同步,volatile的含义就是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我。(【转自www.bitsCN.com 】)

 

volatile 关键字修饰的字段可被多个并发执行线程修改。声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。这样可以确保该字段在任何时间呈现的都是最新的值。

 

volatile 修饰符通常用于由多个线程访问而不使用 lock 语句(C# 参考) 语句对访问进行序列化的字段

 

volatile 关键字可应用于以下类型的字段:

 

Any reference type.

Any pointer type (in an unsafe context).

The types sbyte, byte, short, ushort, int, uint, char, float, bool.

An enum type with an enum base type of byte, sbyte, short, ushort, int, or uint.

1.2 Code Demo

using System;

using System.Threading;

 

public class Worker

{

    // This method will be 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;

    }

    // Volatile is used as hint to the compiler that this data

    // member will be accessed by multiple threads.

    private volatile bool _shouldStop;

}

public class WorkerThreadExample

{

    static void Main()

    {

        // Create the 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 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 Join method to block the current thread

        // until the object's thread terminates.

        workerThread.Join();

        Console.WriteLine("main thread: Worker thread has terminated.");

    }

}

2System.Threading.Interlocked

2.1 说明

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

    此类的方法可以防止可能在下列情况发生的错误:计划程序在某个线程正在更新可由其他线程访问的变量时切换上下文;或者当两个线程在不同的处理器上并发执行时。

此类的成员不引发异常。

Increment  Decrement 方法递增或递减变量并将结果值存储在单个操作中

Exchange 方法自动交换指定变量的值。CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。比较和交换操作按原子操作执行。

 

2.2 Code Demo  

namespace Robot.H.Thread.Semaphore

{

    using System;

    using System.Threading;

    class MyInterlockedExchangeExampleClass

    {

        //0 for false, 1 for true.

        private static int usingResource = 0;

 

        private static Object currentMso;

        private static Object globalMso = new Object();

        private const int numThreadIterations = 5;

        private const int numThreads = 10;

 

        static void Main()

        {

            Thread myThread;

            Random rnd = new Random();

 

            for (int i = 0; i < numThreads; i++)

            {

                myThread = new Thread(new ThreadStart(MyThreadProc));

                myThread.Name = String.Format("Thread{0}", i + 1);

 

                //Wait a random amount of time before starting next thread.

                Thread.Sleep(rnd.Next(0, 1000));

                myThread.Start();

            }

        }

 

        private static void MyThreadProc()

        {

            for (int i = 0; i < numThreadIterations; i++)

            {

                UseResource();

 

                //Wait 1 second before next attempt.

                Thread.Sleep(1000);

            }

        }

 

        //A simple method that denies reentrancy.

        static bool UseResource()

        {

            //0 indicates that the method is not in use.

            if (0 == Interlocked.Exchange(ref usingResource, 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);

 

                //Release the lock

                Interlocked.Exchange(ref usingResource, 0);

                return true;

            }

            else

            {

                Console.WriteLine("   {0} was denied the lock", Thread.CurrentThread.Name);

                return false;

            }

        }

 

    }

}

3Lock[在线拍卖系统用的就是这种机制]

3.1 说明

lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。此语句的形式如下:

lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)lock (typeof (MyType))  lock ("myLock") 违反此准则:

最佳做法是定义 private 对象来锁定 private static 对象变量来保护所有实例所共有的数据。

所以使用lock应注意几点

    a、如果一个类的实例是public的,最好不要lock(this)。因为使用你的类的人也许不知道你用了lock,如果他new了一个实例,并且对这个实例上锁,就很容易造成死锁。

   b、如果MyTypepublic的,不要lock(typeof(MyType))

c、永远也不要lock一个字符串

3.2 Code demo

namespace Robot.H.Thread.Lock

{

    using System;

    using System.Threading;

    public class BidHandler

    {

        private object thisLock = new object();

        private int    bids = 100;

        public void Bid(object increment)

        {

            lock (thisLock)

            {

                Console.WriteLine("bids before bid :  " + bids.ToString());

                Console.WriteLine("Amount to bid        : +" + increment.ToString());

                bids += int.Parse(increment.ToString());

                Console.WriteLine("bids after bid  :  " + bids.ToString());

            }

        }

    }

    public class BidClient

    {

        public static void Main()

        {

            Random r = new Random();

            BidHandler handler = new BidHandler();

            Thread[] threads = new Thread[5];

            for (int i = 0; i < 5; i++)

            {

                Thread t = new Thread(new ParameterizedThreadStart(handler.Bid));

                threads[i] = t;

            }

            for (int i = 0; i < 5; i++) {

                threads[i].Start(r.Next(1, 100));

            }

            Thread.Sleep(2000);

            Console.Read();  }}}

4Moniter

4.1 说明

提供同步对对象的访问的机制。

Monitor 类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。

Monitor 具有以下功能:

它根据需要与某个对象相关联。

 

它是未绑定的,也就是说可以直接从任何上下文调用它。

 

不能创建 Monitor 类的实例。

 

将为每个同步对象来维护以下信息:

 

对当前持有锁的线程的引用。

 

对就绪队列的引用,它包含准备获取锁的线程。

 

对等待队列的引用,它包含正在等待锁定对象状态变化通知的线程。

Monitor类提供了与lock类似的功能,不过与lock不同的是,它能更好的控制同步块,当调用了MonitorEnter(Object o)方法时,会获取o的独占权,直到调用Exit(Object o)方法时,才会释放对o的独占权,可以多次调用Enter(Object o)方法,只需要调用同样次数的Exit(Object o)方法即可,Monitor类同时提供了TryEnter(Object o,[int])的一个重载方法,该方法尝试获取o对象的独占权,当获取独占权失败时,将返回false

如果临界区跨越整个方法,则可以通过将 System.Runtime.CompilerServices.MethodImplAttribute 放置在方法上并在 MethodImplAttribute 的构造函数中指定Synchronized 值来实现上述锁定功能。使用该属性后就不需要 Enter  Exit 语句了。请注意,该属性将使当前线程持有锁,直到方法返回;如果可以更早释放锁,则使用 Monitor 类或C#lock 语句而不是该属性。

当选择要同步的对象时,应只锁定私有或内部对象。锁定外部对象可能导致死锁,这是因为不相关的代码可能会出于不同的目的而选择锁定相同的对象。

 

  但使用 lock 通常比直接使用 Monitor 更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过finally 中调用Exit来实现的。事实上,lock 就是用 Monitor 类来实现的。下面两段代码是等效的:

lock (x)

{

DoSomething();

}

等效于

 

object obj = ( object )x;

System.Threading.Monitor.Enter(obj);

try

{

DoSomething();

}

finally

{

System.Threading.Monitor.Exit(obj);

}

 

4.2 Code demo

namespace Robot.H.Thread.Moniter

{

    using System;

    using System.Collections;

    using System.Threading;

    /// <summary>

    /// Summary description for Class1.

    /// </summary>

    class MonitorSample

    {

        //Define the queue to safe thread access.

        private Queue m_inputQueue;

 

        public MonitorSample()

        {

            m_inputQueue = new Queue();

        }

 

        //Add an element to the queue and obtain the monitor lock for the queue object.

        public void AddElement(object qValue)

        {

            //Lock the queue.

            Monitor.Enter(m_inputQueue);

            //Add element

            m_inputQueue.Enqueue(qValue);

            //Unlock the queue.

            Monitor.Exit(m_inputQueue);

        }

 

        //Try to add an element to the queue.

        //Add the element to the queue only if the queue object is unlocked.

        public bool AddElementWithoutWait(object qValue)

        {

            //Determine whether the queue is locked

            if (!Monitor.TryEnter(m_inputQueue))

                return false;

            m_inputQueue.Enqueue(qValue);

 

            Monitor.Exit(m_inputQueue);

            return true;

        }

 

        //Try to add an element to the queue.

        //Add the element to the queue only if during the specified time the queue object will be unlocked.

        public bool WaitToAddElement(object qValue, int waitTime)

        {

            //Wait while the queue is locked.

            if (!Monitor.TryEnter(m_inputQueue, waitTime))

                return false;

            m_inputQueue.Enqueue(qValue);

            Monitor.Exit(m_inputQueue);

 

            return true;

        }

 

        //Delete all elements that equal the given object and obtain the monitor lock for the queue object.

        public void DeleteElement(object qValue)

        {

            //Lock the queue.

            Monitor.Enter(m_inputQueue);

            int counter = m_inputQueue.Count;

            while (counter > 0)

            {

                //Check each element.

                object elm = m_inputQueue.Dequeue();

                if (!elm.Equals(qValue))

                {

                    m_inputQueue.Enqueue(elm);

                }

                --counter;

            }

            //Unlock the queue.

            Monitor.Exit(m_inputQueue);

        }

 

        //Print all queue elements.

        public void PrintAllElements()

        {

            //Lock the queue.

            Monitor.Enter(m_inputQueue);

            IEnumerator elmEnum = m_inputQueue.GetEnumerator();

            while (elmEnum.MoveNext())

            {

                //Print the next element.

                Console.WriteLine(elmEnum.Current.ToString());

            }

            //Unlock the queue.

            Monitor.Exit(m_inputQueue);

        }

 

        static void Main(string[] args)

        {

            MonitorSample sample = new MonitorSample();

 

            for (int i = 0; i < 30; i++)

                sample.AddElement(i);

            sample.PrintAllElements();

            sample.DeleteElement(0);

            sample.DeleteElement(10);

            sample.DeleteElement(20);

            sample.PrintAllElements();

 

            Console.Read();

 

        }

    }

}

5Mutex

5.1 说明

一个同步基元,也可用于进程间同步。

 

两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。

 

可以使用 WaitHandle.WaitOne 方法请求互斥体的所属权。拥有互斥体的线程可以在对 WaitOne 的重复调用中请求相同的互斥体而不会阻止其执行。但线程必须调用ReleaseMutex 方法同样多的次数以释放互斥体的所属权。Mutex 类强制线程标识,因此互斥体只能由获得它的线程释放。如果线程释放不是它拥有的 mutex,则会在该线程中引发ApplicationException 相反,Semaphore 类不强制线程标识。

 

如果线程在拥有互斥体时终止,则称此互斥体被放弃。此互斥体被设置为终止状态,下一个等待的线程获得所属权。如果没有线程拥有互斥体,则互斥体状态为终止。从 .NET Framework 2.0 版开始,需要该互斥体的下一个线程将引发 AbandonedMutexException。在 .NET Framework 2.0 版之前,这样不会引发任何异常。

 

互斥体有两种类型:局部互斥体和已命名的系统互斥体。如果使用接受名称的构造函数创建 Mutex 对象,则该对象与具有该名称的操作系统对象关联。已命名的系统互斥体在整个操作系统中都可见,可用于同步进程活动。您可以创建多个 Mutex 对象来表示同一个已命名的系统互斥体,也可以使用 OpenExisting 方法打开现有的已命名系统互斥体。

 

局部互斥体仅存在于您的进程内。您的进程中任何引用局部 Mutex 对象的线程都可以使用它。每个 Mutex 对象都是一个单独的局部互斥体。

 

可以使用 Mutex 对象提供对资源的独占访问。Mutex 类比 Monitor 类使用更多系统资源,但是它可以跨应用程序域边界进行封送处理,可用于多个等待,并且可用于同步不同进程中的线程。

5.2 Code demo

//This example shows how a Mutex is used to synchronize access

namespace Robot.H.Thread.Mutex

{

    using System;

    using System.Threading;

    public class BidHandler

    {

        private static Mutex mut = new Mutex();

        private int    bids = 100;

 

        // This method represents a resource that must be synchronized

        // so that only one thread at a time can enter.

        public void Bid(object increment)

        {

                // Wait until it is safe to enter.

                mut.WaitOne();

                Console.WriteLine("{0} has entered the protected area",Thread.CurrentThread.Name);

                Console.WriteLine("bids before bid :  " + bids.ToString());

                Console.WriteLine("Amount to bid        : +" + increment.ToString());

                bids += int.Parse(increment.ToString());

                Console.WriteLine("bids after bid  :  " + bids.ToString());

                Console.WriteLine("{0} is leaving the protected area\r\n",Thread.CurrentThread.Name);

                // Release the Mutex.

                mut.ReleaseMutex();

        }

    }

    public class BidClient

    {

        public static void Main()

        {

            Random r = new Random();

            BidHandler handler = new BidHandler();

            Thread[] threads = new Thread[5];

 

            for (int i = 0; i < 5; i++)

            {

                Thread t = new Thread(new ParameterizedThreadStart(handler.Bid));

                t.Name = string.Format("tread{0}",i);

                threads[i] = t;

            }

            for (int i = 0; i < 5; i++) {

                threads[i].Start(r.Next(1, 100));

            }

            Thread.Sleep(2000);

            Console.Read();

        }

    }

 

}

6Semaphore

6.1 说明

限制可同时访问某一资源或资源池的线程数。

使用 Semaphore 类可控制对资源池的访问。线程通过调用 WaitOne 方法(从 WaitHandle 类继承)进入信号量,并通过调用 Release 方法释放信号量。

信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。

被阻止的线程并不一定按特定的顺序(如 FIFO  LIFO)进入信号量。

 

被阻止的线程并不一定按特定的顺序(如 FIFO  LIFO)进入信号量。

 

信号量分为两种类型:局部信号量和已命名的系统信号量。如果您使用接受名称的构造函数创建 Semaphore 对象,则该对象与具有该名称的操作系统信号量关联。已命名的系统信号量在整个操作系统中都可见,可用于同步进程活动。您可以创建多个 Semaphore 对象来表示同一个已命名的系统信号量,也可以使用 OpenExisting 方法打开现有的已命名系统信号量。

 

局部信号量仅存在于您的进程内。您的进程中任何引用局部 Semaphore 对象的线程都可以使用它。每个 Semaphore 对象都是一个单独的局部信号量。

 

Semaphore 类不对向 WaitOne  Release 方法发出的调用强制线程标识。例如,信号量的一个常用方案包括一个生产者线程和一个使用者线程,其中一个线程总是增加信号量计数,而另一个线程总是减少信号量计数。

 

编程人员应负责确保线程释放信号量的次数不会过多。例如,假定信号量的最大计数为二,线程 A 和线程 B 都进入信号量。如果线程 B 中发生了一个编程错误,导致它调用Release 两次,则两次调用都会成功。这样,信号量的计数就已经达到了最大值,所以,当线程 A 最终调用 Release 时,将引发 SemaphoreFullException

6.2 CodeDemo

namespace Robot.H.Thread.Semaphore{

    using System;

    using System.Threading;

    public class BidHandler{

        private static Semaphore _pool = new Semaphore(1, 1);

        private int bids = 100;

        public void Bid(object increment){

            // Each worker thread begins by requesting the

            // semaphore.

            Console.WriteLine("{0} begins " +

            "and waits for the semaphore.", Thread.CurrentThread.Name);

            _pool.WaitOne();

            Console.WriteLine("Thread {0} enters the semaphore.", Thread.CurrentThread.Name);

            Console.WriteLine("bids before bid :  " + bids.ToString());

            Console.WriteLine("Amount to bid        : +" + increment.ToString());

            bids += int.Parse(increment.ToString());

            Console.WriteLine("bids after bid  :  " + bids.ToString());

            Console.WriteLine("Thread {0} releases the semaphore.\r\n", Thread.CurrentThread.Name);

            _pool.Release();

        }

    }

    public class BidClient{

        public static void Main(){

            Random r = new Random();

            BidHandler handler = new BidHandler();

            Thread[] threads = new Thread[5];

 

            for (int i = 0; i < 5; i++){

                Thread t = new Thread(new ParameterizedThreadStart(handler.Bid));

                t.Name = string.Format("tread{0}", i);

                threads[i] = t;

            }

            for (int i = 0; i < 5; i++){

                threads[i].Start(r.Next(1, 100));

            }

            Thread.Sleep(2000);

            Console.Read();}}}

      

 

7ReaderWriterLock

7.1 说明

定义支持单个写线程和多个读线程的锁。

ReaderWriterLock 用于同步对资源的访问。在任一特定时刻,它允许多个线程同时进行读访问,或者允许单个线程进行写访问。在资源不经常发生更改的情况下,ReaderWriterLock 所提供的吞吐量比简单的一次只允许一个线程的锁(如 Monitor)更高。

在多数访问为读访问,而写访问频率较低、持续时间也比较短的情况下,ReaderWriterLock 的性能最好。多个读线程与单个写线程交替进行操作,所以读线程和写线程都不会长时间阻止。

长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。

一个线程可以持有读线程锁或写线程锁,但是不能同时持有两者。若要获取写线程锁,请使用 UpgradeToWriterLock  DowngradeFromWriterLock,而不要通过释放读线程锁的方式获取。

读线程和写线程将分别排入各自的队列。当线程释放写线程锁时,此刻读线程队列中的所有等待线程都将被授予读线程锁;当已释放所有读线程锁时,写线程队列中处于等待状态的下一个线程(如果存在)将被授予写线程锁,依此类推。换句话说,ReaderWriterLock 在一组读线程和一个写线程之间交替进行操作。

当写线程队列中有一个线程在等待活动读线程锁被释放时,请求新的读线程锁的线程会排入读线程队列。即使它们能和现有的阅读器锁持有者共享并发访问,也不会给它们的请求授予权限;这有助于防止编写器被阅读器无限期阻止

大多数在 ReaderWriterLock 上获取锁的方法都采用超时值。使用超时可以避免应用程序中出现死锁。例如,某个线程可能获取了一个资源上的写线程锁,然后请求第二个资源上的读线程锁;同时,另一个线程获取了第二个资源上的写线程锁,并请求第一个资源上的读线程锁。如果不使用超时,这两个线程将出现死锁。

如果超时间隔过期并且没有授予锁请求,则此方法通过引发 ApplicationException 将控制返回给调用线程。线程可以捕捉此异常并确定下一步要进行的操作。

说明

-1

Infinite.

0

无超时。

> 0

要等待的毫秒数。

除了 -1 以外,不允许使用负的超时值。如果要使用 -1 以外的负整数来指定超时,系统将使用零(无超时)。如果指定的 TimeSpan 表示的是 -1 以外的负毫秒数,将引发ArgumentOutOfRangeException

7.2 CodeDemo

// This example shows a ReaderWriterLock protecting a shared

// resource that is read concurrently and written exclusively

// by multiple threads.

 

// The complete code is located in the ReaderWriterLock

// class topic.

using System;

using System.Threading;

 

public class Test

{

    // Declaring the ReaderWriterLock at the class level

    // makes it visible to all threads.

    static ReaderWriterLock rwl = new ReaderWriterLock();

    // For this example, the shared resource protected by the

    // ReaderWriterLock is just an integer.

    static int resource = 0;

 

    const int numThreads = 26;

    static bool running = true;

    static Random rnd = new Random();

 

    // Statistics.

    static int readerTimeouts = 0;

    static int writerTimeouts = 0;

    static int reads = 0;

    static int writes = 0;

 

    public static void Main(string[] args)

    {

        // Start a series of threads. Each thread randomly

        // performs reads and writes on the shared resource.

        Thread[] t = new Thread[numThreads];

        for (int i = 0; i < numThreads; i++)

        {

            t[i] = new Thread(new ThreadStart(ThreadProc));

            t[i].Name = new String(Convert.ToChar(i + 65), 1);

            t[i].Start();

            if (i > 10)

                Thread.Sleep(300);

        }

 

        // Tell the threads to shut down, then wait until they all

        // finish.

        running = false;

        for (int i = 0; i < numThreads; i++)

        {

            t[i].Join();

        }

 

        // Display statistics.

        Console.WriteLine("\r\n{0} reads, {1} writes, {2} reader time-outs, {3} writer time-outs.",

            reads, writes, readerTimeouts, writerTimeouts);

        Console.WriteLine("Press ENTER to exit.");

        Console.ReadLine();

    }

 

    static void ThreadProc()

    {

        // As long as a thread runs, it randomly selects

        // various ways to read and write from the shared

        // resource. Each of the methods demonstrates one

        // or more features of ReaderWriterLock.

        while (running)

        {

            double action = rnd.NextDouble();

            if (action < .8)

                ReadFromResource(10);

            else if (action < .81)

                ReleaseRestore(50);

            else if (action < .90)

                UpgradeDowngrade(100);

            else

                WriteToResource(100);

        }

    }

 

    // Shows how to request and release a reader lock, and

    // how to handle time-outs.

    static void ReadFromResource(int timeOut)

    {

        try

        {

            rwl.AcquireReaderLock(timeOut);

            try

            {

                // It is safe for this thread to read from

                // the shared resource.

                Display("reads resource value " + resource);

                Interlocked.Increment(ref reads);

            }

            finally

            {

                // Ensure that the lock is released.

                rwl.ReleaseReaderLock();

            }

        }

        catch (ApplicationException)

        {

            // The reader lock request timed out.

            Interlocked.Increment(ref readerTimeouts);

        }

    }

 

    // Shows how to request and release the writer lock, and

    // how to handle time-outs.

    static void WriteToResource(int timeOut)

    {

        try

        {

            rwl.AcquireWriterLock(timeOut);

            try

            {

                // It is safe for this thread to read or write

                // from the shared resource.

                resource = rnd.Next(500);

                Display("writes resource value " + resource);

                Interlocked.Increment(ref writes);

            }

            finally

            {

                // Ensure that the lock is released.

                rwl.ReleaseWriterLock();

            }

        }

        catch (ApplicationException)

        {

            // The writer lock request timed out.

            Interlocked.Increment(ref writerTimeouts);

        }

    }

 

    // Shows how to request a reader lock, upgrade the

    // reader lock to the writer lock, and downgrade to a

    // reader lock again.

    static void UpgradeDowngrade(int timeOut)

    {

        try

        {

            rwl.AcquireReaderLock(timeOut);

            try

            {

                // It is safe for this thread to read from

                // the shared resource.

                Display("reads resource value " + resource);

                Interlocked.Increment(ref reads);

 

                // If it is necessary to write to the resource,

                // you must either release the reader lock and

                // then request the writer lock, or upgrade the

                // reader lock. Note that upgrading the reader lock

                // puts the thread in the write queue, behind any

                // other threads that might be waiting for the

                // writer lock.

                try

                {

                    LockCookie lc = rwl.UpgradeToWriterLock(timeOut);

                    try

                    {

                        // It is safe for this thread to read or write

                        // from the shared resource.

                        resource = rnd.Next(500);

                        Display("writes resource value " + resource);

                        Interlocked.Increment(ref writes);

                    }

                    finally

                    {

                        // Ensure that the lock is released.

                        rwl.DowngradeFromWriterLock(ref lc);

                    }

                }

                catch (ApplicationException)

                {

                    // The upgrade request timed out.

                    Interlocked.Increment(ref writerTimeouts);

                }

 

                // When the lock has been downgraded, it is

                // still safe to read from the resource.

                Display("reads resource value " + resource);

                Interlocked.Increment(ref reads);

            }

            finally

            {

                // Ensure that the lock is released.

                rwl.ReleaseReaderLock();

            }

        }

        catch (ApplicationException)

        {

            // The reader lock request timed out.

            Interlocked.Increment(ref readerTimeouts);

        }

    }

 

    // Shows how to release all locks and later restore

    // the lock state. Shows how to use sequence numbers

    // to determine whether another thread has obtained

    // a writer lock since this thread last accessed the

    // resource.

    static void ReleaseRestore(int timeOut)

    {

        int lastWriter;

 

        try

        {

            rwl.AcquireReaderLock(timeOut);

            try

            {

                // It is safe for this thread to read from

                // the shared resource. Cache the value. (You

                // might do this if reading the resource is

                // an expensive operation.)

                int resourceValue = resource;

                Display("reads resource value " + resourceValue);

                Interlocked.Increment(ref reads);

 

                // Save the current writer sequence number.

                lastWriter = rwl.WriterSeqNum;

 

                // Release the lock, and save a cookie so the

                // lock can be restored later.

                LockCookie lc = rwl.ReleaseLock();

 

                // Wait for a random interval (up to a

                // quarter of a second), and then restore

                // the previous state of the lock. Note that

                // there is no time-out on the Restore method.

                Thread.Sleep(rnd.Next(250));

                rwl.RestoreLock(ref lc);

 

                // Check whether other threads obtained the

                // writer lock in the interval. If not, then

                // the cached value of the resource is still

                // valid.

                if (rwl.AnyWritersSince(lastWriter))

                {

                    resourceValue = resource;

                    Interlocked.Increment(ref reads);

                    Display("resource has changed " + resourceValue);

                }

                else

                {

                    Display("resource has not changed " + resourceValue);

                }

            }

            finally

            {

                // Ensure that the lock is released.

                rwl.ReleaseReaderLock();

            }

        }

        catch (ApplicationException)

        {

            // The reader lock request timed out.

            Interlocked.Increment(ref readerTimeouts);

        }

    }

 

    // Helper method briefly displays the most recent

    // thread action. Comment out calls to Display to

    // get a better idea of throughput.

    static void Display(string msg)

    {

        Console.Write("Thread {0} {1}.       \r", Thread.CurrentThread.Name, msg);

    }

}

 

 

 

 

8:同步事件和等待句柄

8.1 说明

lockMonitor可以很好地起到线程同步的作用,但它们无法实现线程之间传递事件。如果要实现线程同步的同时,线程之间还要有交互,就要用到同步事件。同步事件是有两个状态(终止和非终止)的对象,它可以用来激活和挂起线程。

 

同步事件有两种:AutoResetEvent ManualResetEvent。它们之间唯一不同的地方就是在激活线程之后,状态是否自动由终止变为非终止。AutoResetEvent自动变为非终止,就是说一个AutoResetEvent只能激活一个线程。而ManualResetEvent要等到它的Reset方法被调用,状态才变为非终止,在这之前,ManualResetEvent可以激活任意多个线程。

 

可以调用WaitOneWaitAnyWaitAll来使线程等待事件。它们之间的区别可以查看MSDN。当调用事件的 Set方法时,事件将变为终止状态,等待的线程被唤醒。

 

8.2 CodeDemo

 

using System;

using System.Threading;

 

class ThreadingExample

{

    static AutoResetEvent autoEvent;

 

    static void DoWork()

    {

        Console.WriteLine("   worker thread started, now waiting on event...");

        autoEvent.WaitOne();

        Console.WriteLine("   worker thread reactivated, now exiting...");

    }

 

    static void Main()

    {

        autoEvent = new AutoResetEvent(false);

 

        Console.WriteLine("main thread starting worker thread...");

        Thread t = new Thread(DoWork);

        t.Start();

 

        Console.WriteLine("main thrad sleeping for 1 second...");

        Thread.Sleep(1000);

 

        Console.WriteLine("main thread signaling worker thread...");

        autoEvent.Set();

    }

}

 

 

 

接触这方面的知识不久,欢迎大家多多提宝贵意见。希望能和更多的朋友一起交流、学习。

posted @ 2010-01-20 15:44  cchess  阅读(395)  评论(2编辑  收藏  举报