『C#基础』多线程笔记「二」线程同步

来源:互联网 发布:剑灵捏脸数据百度网盘 编辑:程序博客网 时间:2024/06/10 19:46

文章结构:

  1. 锁定
  2. 监视器
  3. 共享资源的同步访问
  4. 同步事件和等待句柄
  5. 多线程使用准则「MSDN」

 

锁定

无论是程序还是数据库,只要是涉及到并发的问题,都难免会有「锁」的概念。

在C#中,使用lock关键字来对某个对象实施加锁的操作。

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

lock调用            public static string lockTest = string.Empty;  // 用于测试线程锁                     。。。。。。            /* 关于lock关键字:             *     lock必须有一个参数对象,且这个参数对象必须是引用类型的,也就是说,不可以是int等;             *     参数对象不可以是用来定义锁的范围的,这个范围主要取决于参数对象的可访问限制,private/internal/public等;             *     这个提供的对象是用来唯一的标识由多个线程共享的资源,也通常表示需要进行线程同步的资源;             *     也就是说,当资源对象「R1」被线程「T1」锁住后,假如线程「T2」想要使用「R1」,就必须要等「T1」执行完才可以;             *     尽量不是锁定「public」类型的对象,特别是字条串,因为这样可能会造成不可预知的问题;             *     也就是说,如果我定义了一个资源「R1」,我在「T1」中要使用「R1」,所以我在「T1」中执行lock(「R1」),             *     然后,「T2」也想要访问「R1」,但是因为此时「R1」已经被「T1」锁定,所以「T2」的申请是失败的,             *     所以「T2」会开始等待,等待「T1」执行完并释放「R1」;             *                  */            lock (lockTest)            {                lockTest = Thread.CurrentThread.Name;                Console.WriteLine("\n\n" + Thread.CurrentThread.Name + ":\tLock Block starting...");                for (int i = 0; i < 10; i++)                    Console.WriteLine(lockTest + ":\t"+i+"th");                Console.WriteLine(Thread.CurrentThread.Name + ":\tLock Block end." + "\n\n");            }

监视器

当多个线程公用一个对象的时候,应该使用监视器类「System.Threading.Monitor」,而不是锁「lock」。

首先,Monitor的作用与lock关键字类似,都是防止多个线程同时执行代码块。

            object objLock = null;            lock (objLock)            {                // To do something...            }         // 等效于            object objMonitor = null;            System.Threading.Monitor.Enter(objMonitor);            try            {                // To do something...            }            finally            {                System.Threading.Monitor.Exit(objMonitor);            }

其次,在对小型的代码块进行操作的时候,显然使用lock关键字更加的简洁。

但是,如果要实现一个很复杂的逻辑,或者要根据线程的远行情况来对当前锁定的对象进行控制的话,显然Monitor就更加的方便了。

而且,就MSDN上的说明,lock关键字的内部实现也是使用的Monitor,只不过其只是调用了「Enter」「Exit」这两个方法,并且加代码块中的代码放于「Try…Finally…」中。

在使用的时候,一定要注意的就是Monitor只是针对引用类型对象的操作,而不是对值类型的操作。

如果使用了值类型,就会引发「从不同步的代码块中调用了对象同步方法」。

这主要是因为:

Monitor 将锁定对象(即引用类型)而非值类型。 在您将值类型传递给EnterExit 时,它会针对每个调用分别装箱。 由于每个调用都创建一个单独的对象,所以 Enter 从不拦截,并且其旨在保护的代码并未真正同步。 此外,传递给 Exit 的对象不同于传递给 Enter 的对象,所以 Monitor 将引发 SynchronizationLockException,并显示消息“从不同步的代码块中调用了对象同步方法”。下面的示例演示这些问题。

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

 

名称说明Enter(Object)在指定对象上获取排他锁。Enter(Object, Boolean)获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。Exit释放指定对象上的排他锁。Pulse通知等待队列中的线程锁定对象状态的更改。PulseAll通知所有的等待线程对象状态的更改。TryEnter(Object)尝试获取指定对象的排他锁。TryEnter(Object, Boolean)尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。TryEnter(Object, Int32)在指定的毫秒数内尝试获取指定对象上的排他锁。TryEnter(Object, TimeSpan)在指定的时间量内尝试获取指定对象上的排他锁。TryEnter(Object, Int32, Boolean)在指定的毫秒数中,尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。TryEnter(Object, TimeSpan, Boolean)在指定的一段时间内,尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。Wait(Object)释放对象上的锁并阻止当前线程,直到它重新获取该锁。Wait(Object, Int32)释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果指定的超时间隔已过,则线程进入就绪队列。Wait(Object, TimeSpan)释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果指定的超时间隔已过,则线程进入就绪队列。Wait(Object, Int32, Boolean)释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果指定的超时间隔已过,则线程进入就绪队列。 此方法还指定是否在等待之前退出上下文的同步域(如果处于同步上下文中的话)然后重新获取该同步域。Wait(Object, TimeSpan, Boolean)释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果指定的超时间隔已过,则线程进入就绪队列。 可以在等待之前退出同步上下文的同步域,随后重新获取该域。
「MSDN」中关于「Monitor」的例子using System;using System.Threading;// Note: The class whose internal public member is the synchronizing// method is not public; none of the client code takes a lock on the// Resource object.The member of the nonpublic class takes the lock on// itself. Written this way, malicious code cannot take a lock on// a public object.class SyncResource{    public void Access(Int32 threadNum)    {        // Uses Monitor class to enforce synchronization.        lock (this)        {            // Synchronized: Despite the next conditional, each thread            // waits on its predecessor.            if (threadNum % 2 == 0)            {                Thread.Sleep(2000);            }            Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum);            Thread.Sleep(200);            Console.WriteLine("Stop Synched Resource access  (Thread={0})", threadNum);        }    }}// Without the lock, the method is called in the order in which threads reach it.class UnSyncResource{    public void Access(Int32 threadNum)    {        // Does not use Monitor class to enforce synchronization.        // The next call throws the thread order.        if (threadNum % 2 == 0)        {            Thread.Sleep(2000);        }        Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum);        Thread.Sleep(200);        Console.WriteLine("Stop UnSynched Resource access  (Thread={0})", threadNum);    }}public class App{    static Int32 numAsyncOps = 5;    static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);    static SyncResource SyncRes = new SyncResource();    static UnSyncResource UnSyncRes = new UnSyncResource();    public static void Main()    {        for (Int32 threadNum = 0; threadNum < 5; threadNum++)        {            ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource), threadNum);        }        // Wait until this WaitHandle is signaled.        asyncOpsAreDone.WaitOne();        Console.WriteLine("\t\nAll synchronized operations have completed.\t\n");        // Reset the thread count for unsynchronized calls.        numAsyncOps = 5;        for (Int32 threadNum = 0; threadNum < 5; threadNum++)        {            ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource), threadNum);        }        // Wait until this WaitHandle is signaled.        asyncOpsAreDone.WaitOne();        Console.WriteLine("\t\nAll unsynchronized thread operations have completed.");    }    // The callback method's signature MUST match that of a    // System.Threading.TimerCallback delegate (it takes an Object    // parameter and returns void).    static void SyncUpdateResource(Object state)    {        // This calls the internal synchronized method, passing        // a thread number.        SyncRes.Access((Int32) state);        // Count down the number of methods that the threads have called.        // This must be synchronized, however; you cannot know which thread        // will access the value **before** another thread's incremented        // value has been stored into the variable.        if (Interlocked.Decrement(ref numAsyncOps) == 0)        {            // Announce to Main that in fact all thread calls are done.            asyncOpsAreDone.Set();        }    }    // The callback method's signature MUST match that of a    // System.Threading.TimerCallback delegate (it takes an Object    // parameter and returns void).    static void UnSyncUpdateResource(Object state)    {        // This calls the unsynchronized method, passing a thread number.        UnSyncRes.Access((Int32) state);        // Count down the number of methods that the threads have called.        // This must be synchronized, however; you cannot know which thread        // will access the value **before** another thread's incremented        // value has been stored into the variable.        if (Interlocked.Decrement(ref numAsyncOps) == 0)        {            // Announce to Main that in fact all thread calls are done.            asyncOpsAreDone.Set();        }    }}
            try            {                if (Monitor.TryEnter(workerA))                {                    Monitor.PulseAll(workerA);                    monitorTest = "Monitor\t" + Thread.CurrentThread.Name;                    Console.WriteLine("\n\n" + Thread.CurrentThread.Name + ":\tLock Block starting...");                    for (int i = 0; i < 10; i++)                        Console.WriteLine(workerA.WorkerName + ":\t" + i + "th");                    Console.WriteLine(Thread.CurrentThread.Name + ":\tLock Block end." + "\n\n");                    Monitor.PulseAll(workerA);                }            }            finally            {                Monitor.Exit(workerA);            }

共享资源的同步访问

在多线程环境中保护模块中的全局数据:

  1. 应该程序中所有的线程都可以访问方法中的公用字段。
  2. 要同步对公用字段的访问,可以使用属性替代字段,并使用ReaderWriterLock对象控制访问。
  3. ReaderWriterLock:定义支持单个写线程和多个读线程的锁。
  4. 要注意ReaderWriterLock的ReleaseLockReleaseReaderLockReleaseWriterLock方法。
  5. 「MSDN」建议使用 ReaderWriterLockSlim 而不是 ReaderWriterLock。
  6. 长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。 为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。
// 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);    }}

同步事件和等待句柄

同步事件:

  1. AutoResetEvent:只要激活线程,它的状态将自动从终止变为非终止
  2. ManualResetEvent:允许它的终止状态激活任意多个线程,只有它的Reset方法被调用的时候才还原到非终止状态。
using System;using System.Threading;// Visual Studio: Replace the default class in a Console project with //                the following class.class Example{    private static AutoResetEvent event_1 = new AutoResetEvent(true);    private static AutoResetEvent event_2 = new AutoResetEvent(false);    static void Main()    {        Console.WriteLine("Press Enter to create three threads and start them.\r\n" +                          "The threads wait on AutoResetEvent #1, which was created\r\n" +                          "in the signaled state, so the first thread is released.\r\n" +                          "This puts AutoResetEvent #1 into the unsignaled state.");        Console.ReadLine();        for (int i = 1; i < 4; i++)        {            Thread t = new Thread(ThreadProc);            t.Name = "Thread_" + i;            t.Start();        }        Thread.Sleep(250);        for (int i = 0; i < 2; i++)        {            Console.WriteLine("Press Enter to release another thread.");            Console.ReadLine();            event_1.Set();            Thread.Sleep(250);        }        Console.WriteLine("\r\nAll threads are now waiting on AutoResetEvent #2.");        for (int i = 0; i < 3; i++)        {            Console.WriteLine("Press Enter to release a thread.");            Console.ReadLine();            event_2.Set();            Thread.Sleep(250);        }        // Visual Studio: Uncomment the following line.        //Console.Readline();    }    static void ThreadProc()    {        string name = Thread.CurrentThread.Name;        Console.WriteLine("{0} waits on AutoResetEvent #1.", name);        event_1.WaitOne();        Console.WriteLine("{0} is released from AutoResetEvent #1.", name);        Console.WriteLine("{0} waits on AutoResetEvent #2.", name);        event_2.WaitOne();        Console.WriteLine("{0} is released from AutoResetEvent #2.", name);        Console.WriteLine("{0} ends.", name);    }}/* This example produces output similar to the following:Press Enter to create three threads and start them.The threads wait on AutoResetEvent #1, which was createdin the signaled state, so the first thread is released.This puts AutoResetEvent #1 into the unsignaled state.Thread_1 waits on AutoResetEvent #1.Thread_1 is released from AutoResetEvent #1.Thread_1 waits on AutoResetEvent #2.Thread_3 waits on AutoResetEvent #1.Thread_2 waits on AutoResetEvent #1.Press Enter to release another thread.Thread_3 is released from AutoResetEvent #1.Thread_3 waits on AutoResetEvent #2.Press Enter to release another thread.Thread_2 is released from AutoResetEvent #1.Thread_2 waits on AutoResetEvent #2.All threads are now waiting on AutoResetEvent #2.Press Enter to release a thread.Thread_2 is released from AutoResetEvent #2.Thread_2 ends.Press Enter to release a thread.Thread_1 is released from AutoResetEvent #2.Thread_1 ends.Press Enter to release a thread.Thread_3 is released from AutoResetEvent #2.Thread_3 ends. */

多线程使用准则「MSDN」:

  • 不要使用 Thread.Abort 终止其他线程。 对另一个线程调用 Abort 无异于引发该线程的异常,也不知道该线程已处理到哪个位置。

  • 不要使用 Thread.SuspendThread.Resume 同步多个线程的活动。 请使用 MutexManualResetEventAutoResetEventMonitor

  • 不要从主程序中控制辅助线程的执行(如使用事件), 而应在设计程序时让辅助线程负责等待任务,执行任务,并在完成时通知程序的其他部分。 如果不阻止辅助线程,请考虑使用线程池线程。 如果阻止辅助线程,Monitor.PulseAll 会很有帮助。

  • 不要将类型用作锁定对象。 例如,避免在 C# 中使用 lock(typeof(X)) 代码,或在 Visual Basic 中使用 SyncLock(GetType(X)) 代码,或将Monitor.EnterType 对象一起使用。 对于给定类型,每个应用程序域只有一个 System.Type 实例。 如果您锁定的对象的类型是 public,您的代码之外的代码也可锁定它,但会导致死锁。 有关其他信息,请参见可靠性最佳做法

  • 锁定实例时要谨慎,例如,C# 中的 lock(this) 或 Visual Basic 中的 SyncLock(Me)。 如果您的应用程序中不属于该类型的其他代码锁定了该对象,则会发生死锁。

  • 一定要确保已进入监视器的线程始终离开该监视器,即使当线程在监视器中时发生异常也是如此。 C# 的 lock 语句和 Visual Basic 的 SyncLock 语句可自动提供此行为,它们用一个 finally块来确保调用Monitor.Exit。 如果无法确保调用 Exit,请考虑将您的设计更改为使用Mutex。 Mutex 在当前拥有它的线程终止后会自动释放。

  • 一定要针对那些需要不同资源的任务使用多线程,避免向单个资源指定多个线程。 例如,任何涉及 I/O 的任务都会从其拥有其自己的线程这一点得到好处,因为此线程在 I/O 操作期间将阻止,从而允许其他线程执行。 用户输入是另一种可从专用线程获益的资源。 在单处理器计算机上,涉及大量计算的任务可与用户输入和涉及 I/O 的任务并存,但多个计算量大的任务将相互竞争。

  • 对于简单的状态更改,请考虑使用 Interlocked 类的方法,而不是 lock 语句(在 Visual Basic 中为 SyncLock)。 lock 语句是一个优秀的通用工具,但是Interlocked 类为必须是原子性的更新提供了更好的性能。 如果没有争夺,它会在内部执行一个锁定前缀。 在查看代码时,请注意类似于以下示例所示的代码。 在第一个示例中,状态变量是递增的:

 

其他参考:

  1. http://msdn.microsoft.com/zh-cn/library/ms173179.aspx
  2. http://msdn.microsoft.com/zh-cn/library/z8chs7ft.aspx
  3. http://msdn.microsoft.com/zh-cn/library/dd997305.aspx
  4. http://msdn.microsoft.com/zh-cn/magazine/cc163352.aspx    「死锁监控」
  5. http://msdn.microsoft.com/zh-cn/library/3e8s7xdd.aspx           「托管线程」
原创粉丝点击