『C#基础』多线程笔记「二」线程同步
来源:互联网 发布:剑灵捏脸数据百度网盘 编辑:程序博客网 时间:2024/06/10 19:46
文章结构:
- 锁定
- 监视器
- 共享资源的同步访问
- 同步事件和等待句柄
- 多线程使用准则「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 将锁定对象(即引用类型)而非值类型。 在您将值类型传递给Enter 和 Exit 时,它会针对每个调用分别装箱。 由于每个调用都创建一个单独的对象,所以 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); }
共享资源的同步访问
在多线程环境中保护模块中的全局数据:
- 应该程序中所有的线程都可以访问方法中的公用字段。
- 要同步对公用字段的访问,可以使用属性替代字段,并使用ReaderWriterLock对象控制访问。
- ReaderWriterLock:定义支持单个写线程和多个读线程的锁。
- 要注意ReaderWriterLock的ReleaseLock、ReleaseReaderLock、ReleaseWriterLock方法。
- 「MSDN」建议使用 ReaderWriterLockSlim 而不是 ReaderWriterLock。
- 长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (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); }}
同步事件和等待句柄
同步事件:
- AutoResetEvent:只要激活线程,它的状态将自动从终止变为非终止。
- 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.Suspend 和 Thread.Resume 同步多个线程的活动。 请使用 Mutex、ManualResetEvent、AutoResetEvent 和 Monitor。
不要从主程序中控制辅助线程的执行(如使用事件), 而应在设计程序时让辅助线程负责等待任务,执行任务,并在完成时通知程序的其他部分。 如果不阻止辅助线程,请考虑使用线程池线程。 如果阻止辅助线程,Monitor.PulseAll 会很有帮助。
不要将类型用作锁定对象。 例如,避免在 C# 中使用 lock(typeof(X)) 代码,或在 Visual Basic 中使用 SyncLock(GetType(X)) 代码,或将Monitor.Enter 和 Type 对象一起使用。 对于给定类型,每个应用程序域只有一个 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 类为必须是原子性的更新提供了更好的性能。 如果没有争夺,它会在内部执行一个锁定前缀。 在查看代码时,请注意类似于以下示例所示的代码。 在第一个示例中,状态变量是递增的:
其他参考:
- http://msdn.microsoft.com/zh-cn/library/ms173179.aspx
- http://msdn.microsoft.com/zh-cn/library/z8chs7ft.aspx
- http://msdn.microsoft.com/zh-cn/library/dd997305.aspx
- http://msdn.microsoft.com/zh-cn/magazine/cc163352.aspx 「死锁监控」
- http://msdn.microsoft.com/zh-cn/library/3e8s7xdd.aspx 「托管线程」
- 『C#基础』多线程笔记「二」线程同步
- C#多线程学习笔记(二)之线程同步
- 『C#基础』多线程笔记「四」线程池
- C#中的线程(二) 线程同步基础
- C#中的线程(二) 线程同步基础
- C#中的线程(二) 线程同步基础
- C#中的线程(二) 线程同步基础
- C#中的线程(二) 线程同步基础
- C# 线程(二) 线程同步基础
- C#中的线程(二) 线程同步基础
- c#中多线程---线程同步基础
- C#中的多线程-线程同步基础
- C#中的多线程-线程同步基础
- C#中的多线程-线程同步基础
- 『C#基础』多线程笔记「三」计时器
- C#中的多线程-线程同步基础 (控制线程数量)
- C#中的多线程-线程同步基础 (控制线程数量)
- C#中的多线程-线程同步基础 (控制线程数量)
- Ibatis配置浅析
- ibatis iterate使用
- Nginx+tomcat配置集群负载均衡
- Spring 2.5配置文件详解
- directfb显示中文
- 『C#基础』多线程笔记「二」线程同步
- java 获取当前日期的前三月,前一天
- App Inventor初体验
- 『C#基础』多线程笔记「三」计时器
- 字符串反转
- 『C#基础』多线程笔记「四」线程池
- GB2312简体中文编码表
- mysql清除binlog
- eclipse中将web工程部署到Tomcat的webApps目录下