- 浏览: 245730 次
- 性别:
- 来自: 天津
文章分类
最新评论
-
yulanlian:
...
实现在删除数据后,自增列的值连续 -
RonQi:
楼主写的很好,支持原创!
Google Protocol Buffers
使用C#进行多线程编程经常会用队列池进行线程同步的方法,实现就用到Queue。Queue是线程安全的(Thread safe),但不是泛型的,对象出列时需要进行拆箱转换。也有人会马上想到Queue<T>,但可惜的是泛型Queue<T>却不是线程安全,我们需要用其它编程方法来实现它。
下面介绍一种方法,它能够使用泛型Queue<T>进行线程同步,但是需要用到 lock 关键字以及 AutoResetEvent 和 ManualResetEvent 类对主线程和两个辅助线程进行线程同步。
该示例创建两个辅助线程。一个线程生成元素并将它们存储在非线程安全的泛型队列中。另一个线程使用此队列中的项。另外,主线程定期显示队列的内容,以便该队列可由三个线程进行访问。lock 关键字用于同步对队列的访问,以确保队列的状态不会被破坏。
除了只是使用 lock 关键字来防止同时访问以外,还可以用两个事件对象提供进一步的同步。一个事件对象用来通知辅助线程终止,另一个事件对象由制造者线程用来在有新项添加到队列中时通知使用者线程。这两个事件对象封装在一个名为 SyncEvents 的类中。这使事件可以轻松传递给表示制造者线程和使用者线程的对象。SyncEvents 类按如下方式定义:
public class SyncEvents { public SyncEvents() { _newItemEvent = new AutoResetEvent(false); _exitThreadEvent = new ManualResetEvent(false); _eventArray = new WaitHandle[2]; _eventArray[0] = _newItemEvent; _eventArray[1] = _exitThreadEvent; } public EventWaitHandle ExitThreadEvent { get { return _exitThreadEvent; } } public EventWaitHandle NewItemEvent { get { return _newItemEvent; } } public WaitHandle[] EventArray { get { return _eventArray; } } private EventWaitHandle _newItemEvent; private EventWaitHandle _exitThreadEvent; private WaitHandle[] _eventArray; }
对“新项”事件使用 AutoResetEvent 类,因为您希望每当使用者线程响应此事件后,此事件都能自动重置。或者,将 ManualResetEvent 类用于“退出”事件,因为您希望当此事件终止时有多个线程响应。如果您改为使用 AutoResetEvent,则仅在一个线程响应该事件以后,该事件就还原到非终止状态。另一个线程不会响应,因此在这种情况下,将无法终止。
SyncEvents 类创建两个事件,并将它们以两种不同的形式存储:一种是作为 EventWaitHandle(它是 AutoResetEvent 和 ManualResetEvent 的基类),一种是作为基于 WaitHandle 的数组。如关于使用者线程的讨论中所述,此数组是必需的,因为它使使用者线程可以响应两个事件中的任何一个。
使用者线程和制造者线程分别由名为 Consumer 和 Producer 的类表示。这两个类都定义了一个名为 ThreadRun 的方法。这些方法用作 Main 方法创建的辅助线程的入口点。
Producer 类定义的 ThreadRun 方法如下所示:
public class Producer { public Producer(Queue<int> queue, SyncEvents syncEvents) { _queue = queue; _syncEvents = syncEvents; } Queue<int> _queue; SyncEvents _syncEvents; public void ThreadRun() { int count = 0; Random r = new Random(); while (!_syncEvents.ExitThreadEvent.WaitOne(0, false)) { lock (((ICollection) _queue).SyncRoot) { while (_queue.Count < 20) { _queue.Enqueue(r.Next(0, 100)); _syncEvents.NewItemEvent.Set(); count++; } } } Console.WriteLine("Producer thread: produced {0} items", count); } }
此方法一直循环,直到“退出线程”事件变为终止状态。此事件的状态由 WaitOne 方法使用 SyncEvents 类定义的 ExitThreadEvent 属性进行测试。在这种情况下,检查该事件的状态不会阻止当前线程,因为 WaitOne 使用的第一个参数为零,这表示该方法应立即返回。如果 WaitOne 返回 true,则说明该事件当前处于终止状态。如果是这样,ThreadRun 方法将返回,其效果相当于终止执行此方法的辅助线程。
在“退出线程”事件终止前,Producer.ThreadStart 方法将尝试在队列中保留 20 项。项只是 0 到 100 之间的一个整数。在添加新项前,必须锁定该集合,以防止使用者线程和主线程同时访问该集合。这一点是使用 lock 关键字完成的。传递给 lock 的参数是通过 ICollection 接口公开的 SyncRoot 字段。此字段专门为同步线程访问而提供。对该集合的独占访问权限被授予 lock 后面的代码块中包含的所有指令。对于制造者添加到队列中的每个新项,都将调用“新项”事件的 Set 方法。这将通知使用者线程离开挂起状态并开始处理新项。
Consumer 对象还定义名为 ThreadRun 的方法。与制造者的 ThreadRun 类似,此方法由 Main 方法创建的辅助线程执行。然而,使用者的 ThreadStart 必须响应两个事件。Consumer.ThreadRun 方法如下所示:
public class Consumer { public Consumer(Queue<int> queue, SyncEvents syncEvents) { _queue = queue; _syncEvents = syncEvents; } Queue<int> _queue; SyncEvents _syncEvents; public void ThreadRun() { int count = 0; while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1) { lock (((ICollection) _queue).SyncRoot) { int item = _queue.Dequeue(); } count++; } Console.WriteLine("Consumer Thread: consumed {0} items", count); } }
此方法使用 WaitAny 来阻止使用者线程,直到所提供的数组中的任意一个等待句柄变为终止状态。在这种情况下,数组中有两个句柄,一个用来终止辅助线程,另一个用来指示有新项添加到集合中。WaitAny 返回变为终止状态的事件的索引。“新项”事件是数组中的第一个事件,因此索引零表示新项。在这种情况下,检查索引 1(它指示“退出线程”事件),并使用它来确定此方法是否继续使用项。如果“新项”事件处于终止状态,您将通过 lock 获得对集合的独占访问权限并使用新项。因为此示例生成并使用数千个项,所以不显示使用的每个项,而是使用 Main 定期显示队列中的内容,如下面所演示的那样。
Main 方法首先创建一个队列(该队列的内容将被生成和使用)和 SyncEvents 的一个实例(已在前面演示):
Queue<int> queue = new Queue<int>(); SyncEvents syncEvents = new SyncEvents();
然后,Main 配置 Producer 和 Consumer 对象以供辅助线程使用。然而,此步骤并不创建或启动实际的辅助线程:
Producer producer = new Producer(queue, syncEvents); Consumer consumer = new Consumer(queue, syncEvents); Thread producerThread = new Thread(producer.ThreadRun); Thread consumerThread = new Thread(consumer.ThreadRun); producerThread.Start(); consumerThread.Start();
请注意,队列和同步事件对象作为构造函数参数同时传递给 Consumer 和 Producer 线程。这提供了两个对象,它们具有执行各自任务所需的共享资源。然后创建两个新的 Thread 对象,并使用每个对象的 ThreadRun 方法作为参数。每个辅助线程在启动时都将此参数用作线程的入口点。
接着,Main 通过调用 Start 方法来启动两个辅助线程,如下所示:
producerThread.Start(); consumerThread.Start();
此时,创建了两个新的辅助线程,它们独立于当前正在执行 Main 方法的主线程开始异步执行过程。事实上,Main 接下来要做的事情是通过调用 Sleep 方法将主线程挂起。该方法将当前正在执行的线程挂起指定的时间(毫秒)。在此时间间隔过后,Main 将重新激活,这时它将显示队列的内容。Main 重复此过程四次,如下所示:
//主线程显示 for (int i = 0; i < 12; i++) { Thread.Sleep(1000); ShowQueueContents(queue); }
最后,Main 通过调用“退出线程”事件的 Set 方法通知辅助线程终止,然后对每个辅助线程调用 Join 方法以阻止主线程,直到每个辅助线程都响应该事件并终止。
有一个线程同步的最终示例:ShowQueueContents 方法。与制造者线程和使用者线程类似,此方法使用 lock 获得对队列的独占访问权限。然而在这种情况下,独占访问非常重要,因为 ShowQueueContents 对整个集合进行枚举。对集合进行枚举是一个特别容易由于异步操作而造成数据损坏的操作,因为它需要遍历整个集合的内容。
请注意,ShowQueueContents 是由主线程执行的,因为它被 Main 调用。这意味着,当此方法获得对项队列的独占访问权限时,既阻止了制造者线程访问队列,也阻止了使用者线程访问队列。ShowQueueContents 锁定队列并枚举其内容:
private static void ShowQueueContents(Queue<int> q) { lock (((ICollection) q).SyncRoot) { foreach (int item in q) { Console.Write("{0} ", item); } } Console.WriteLine(); }
完整源代码如下
using System; using System.Collections; using System.Collections.Generic; using System.Threading; namespace cjl { public static class Ubb { static void Main(string[] args) { Queue<int> queue = new Queue<int>(); SyncEvents syncEvents = new SyncEvents(); Producer producer = new Producer(queue, syncEvents); Consumer consumer = new Consumer(queue, syncEvents); Thread producerThread = new Thread(producer.ThreadRun); Thread consumerThread = new Thread(consumer.ThreadRun); producerThread.Start(); consumerThread.Start(); //主线程显示 for (int i = 0; i < 12; i++) { Thread.Sleep(1000); ShowQueueContents(queue); } //设置退出信号,退出 syncEvents.ExitThreadEvent.Set(); } private static void ShowQueueContents(Queue<int> q) { lock (((ICollection) q).SyncRoot) { foreach (int item in q) { Console.Write("{0} ", item); } } Console.WriteLine(); } } public class SyncEvents { public SyncEvents() { _newItemEvent = new AutoResetEvent(false); _exitThreadEvent = new ManualResetEvent(false); _eventArray = new WaitHandle[2]; _eventArray[0] = _newItemEvent; _eventArray[1] = _exitThreadEvent; } public EventWaitHandle ExitThreadEvent { get { return _exitThreadEvent; } } public EventWaitHandle NewItemEvent { get { return _newItemEvent; } } public WaitHandle[] EventArray { get { return _eventArray; } } private EventWaitHandle _newItemEvent; private EventWaitHandle _exitThreadEvent; private WaitHandle[] _eventArray; } public class Producer { public Producer(Queue<int> queue, SyncEvents syncEvents) { _queue = queue; _syncEvents = syncEvents; } Queue<int> _queue; SyncEvents _syncEvents; public void ThreadRun() { int count = 0; Random r = new Random(); while (!_syncEvents.ExitThreadEvent.WaitOne(0, false)) { lock (((ICollection) _queue).SyncRoot) { while (_queue.Count < 20) { _queue.Enqueue(r.Next(0, 100)); _syncEvents.NewItemEvent.Set(); count++; } } } Console.WriteLine("Producer thread: produced {0} items", count); } } public class Consumer { public Consumer(Queue<int> queue, SyncEvents syncEvents) { _queue = queue; _syncEvents = syncEvents; } Queue<int> _queue; SyncEvents _syncEvents; public void ThreadRun() { int count = 0; while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1) { lock (((ICollection) _queue).SyncRoot) { int item = _queue.Dequeue(); } count++; } Console.WriteLine("Consumer Thread: consumed {0} items", count); } } }
发表评论
-
文件读写冲突的解决办法:ReaderWriterLock
2011-04-07 14:59 1268项目中碰到了静态页文件读写冲突的问题(如果同时存在读写就报黄页 ... -
线程,同步与锁——Lock你到底锁住了谁
2011-04-03 14:59 783线程在多核时代的优势 ... -
多线程下WinForm开发应该注意哪些问题?
2011-04-03 14:52 953昨日,与一同事一起在 ... -
在多线程中如何调用Winform
2011-04-03 14:44 891转自 dengsu888666 每一 ... -
关于.NET异步调用的初步总结
2011-04-03 14:42 1008最近看了看.NET异步调用方面的资料,现择重点总结,若有纰漏敬 ... -
对 Windows 窗体控件进行线程安全调用
2011-04-03 14:39 929今天在编写一个windows应用程序的时候碰到了一个小问题,程 ... -
DotNet中异步编程的简单应用
2011-04-03 14:35 540这里说的异步编程并不是AJAX等的Web异步编程,而仅仅是Do ... -
winform程序中如何跨线程修改控件的值
2011-04-03 14:30 1635winform程序是单线程的。 /// <summar ... -
多线程执行多任务的DEMO
2011-04-03 14:21 924这个场景应用比较普遍, 比如多个线程下载多个文件,比如3个线程 ... -
C# 多线程下载
2011-04-03 14:12 1640下面是一个完整的多线程下载源码,我在写代码的时候遇到点问题也放 ... -
.NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke
2011-04-03 14:06 12301,错误的代码是: using System; using ... -
利用委托机制处理.NET中的异常
2011-04-03 14:01 1143转自 terrylee.cnblogs.com 概述 在. ... -
线程安全类 跨线程修改窗体UI
2011-04-03 13:54 878private void ThreadSafeInvoke(C ...
相关推荐
生产者和消费者模式是多线程编程中一个经典的设计模式,它主要解决的是在多线程环境下资源的有效利用和同步问题。在这个模式中,生产者负责生成数据,而消费者负责消费这些数据。为了保证生产与消费的平衡以及避免...
总之,"生产者-消费者"多线程处理是一个核心的并发编程概念,它展示了如何在多线程环境中有效地管理和共享资源,以实现高效的程序执行。理解并正确实现这一模式对于任何从事并发编程或系统设计的IT专业人员来说都至...
"生产者消费者"模式是多线程应用中一个经典的范例,它通过线程间的协作来实现数据处理的高效与同步。这个模式的核心思想是将任务分为两个角色:生产者(Producer)和消费者(Consumer)。生产者负责生成数据,而消费...
在多线程编程中,生产者消费者模型是一种常见的设计模式,用于解决线程间的通信和同步问题。在C#中,我们可以利用各种机制实现这样的队列。本篇将详细讲解如何在C#中构建一个生产消费者队列,以及它如何帮助优化线程...
生产者消费者问题是多线程编程中的一个经典模型,用于演示如何在并发环境中通过共享资源进行协作。在这个模型中,生产者线程负责生成数据,而消费者线程则负责消费这些数据。问题的关键在于如何保证生产者不会在无处...
生产者-消费者模式是一种典型的多线程同步问题,一个或多个生产者线程生成数据,一个或多个消费者线程消耗这些数据。在此模式中,通常使用队列作为缓冲区,同时利用互斥量和条件变量来同步生产者和消费者的动作。 ...
多线程实现生产者消费者模型:锁(Lock)、信号量(Semaphore、BoundedSemaphore)、条件(Condition)、队列(Queue)、事件(Event) 多进程程实现生产者消费者模型:信号量(Semaphore)、条件(Condition)、...
在C#编程中,"生产者消费者模型"是一种常见的多线程设计模式,它通过分离数据的生产和消费过程,使得生产者线程可以专心于创建数据,而消费者线程则专注于处理这些数据,两者互不干扰,提高了系统效率。在这个模型中...
在Java编程中,"线程同步--生产者消费者问题"是一个经典的多线程问题,它涉及到如何有效地在多个线程之间共享资源。这个问题通常用于演示和理解线程间的协作机制,如互斥锁、条件变量等。在此,我们将深入探讨这个...
总之,Java中的生产者-消费者模式是多线程编程中解决数据共享和同步问题的有效手段,通过合理利用`BlockingQueue`等并发工具类,我们可以构建高效、稳定的多线程应用。在开发过程中,了解和掌握这种模式有助于提高...
在多线程环境中,生产者和消费者可能同时运行,因此需要一种机制来确保数据的一致性和完整性,避免数据竞争问题。 在C++中,我们可以利用Windows API中的`CreateSemaphore`函数创建信号量来实现线程同步。信号量...
Java多线程编程是开发高并发、高性能应用的关键技术之一,而生产者消费者模式是多线程编程中常用的一种设计模式。它通过分离数据的生产和消费过程,实现了线程间的协同工作,有效避免了资源的竞争和浪费。在这个模式...
在这个模式中,多个生产者线程生成数据,而多个消费者线程则负责消费这些数据。为了确保数据的一致性和避免资源竞争,我们需要采用有效的同步机制。 首先,我们要理解“生产者-消费者”模型的基础,这是由计算机...
7. **性能优化**:在实现多线程时,要考虑系统资源的使用和性能。过多的线程可能会增加上下文切换的开销,反而降低效率。因此,合理地调度和控制线程数量至关重要。 在实际开发中,我们需要结合业务需求和系统环境...
在 LabVIEW 中,“生产者-消费者”模型是一种常见的多线程编程模式,用于解决数据处理中的同步问题。这个模型的核心思想是将数据的生产和消费分隔开,避免了数据处理过程中的冲突和等待,提高了程序执行效率。 生产...
Java线程实现的生产者和消费者程序是一种经典的多线程设计模式,用于处理并发操作中的数据共享问题。这种模式在实际编程中广泛应用,特别是在需要高效处理数据流和资源管理的系统中。以下将详细讲解其核心概念、实现...
这个C++实现的生产者消费者问题展示了线程间通信和同步的基本方法,它是多线程编程中的重要知识点,对于理解和解决复杂并发问题有着重要意义。通过深入学习和实践,我们可以更好地掌握多线程编程,并在实际项目中...
生产者消费者问题是多线程编程中的一个经典案例,主要探讨如何在并发环境下高效且安全地共享有限资源。在这个问题中,我们通常有两类线程:生产者线程负责生成数据,而消费者线程则负责消费这些数据。为了解决线程间...
通过对这个代码的分析和学习,我们可以更深入地理解多线程同步以及生产者消费者模式在实际应用中的实现方法。 总之,生产者消费者问题是多线程编程中的一个重要概念,它展示了如何通过同步机制来协调不同任务之间的...
在Java编程中,多线程是并发处理任务的关键机制,而生产者-消费者问题是多线程编程中的一个经典模型。这个模型描述了两个角色:生产者(Producer)负责创建资源,消费者(Consumer)则负责消耗这些资源。为了解决...