C#多线程之三:解决多线程编程中大并发数等待唤醒的问题
来源:互联网 发布:淘宝卖家骗局 编辑:程序博客网 时间:2024/06/05 05:33
在执行华工算法分析逻辑之前,调用谷歌api这一步必需全部完成;网络请求是个耗时的过程,故对每一个请求开启单独的线程(同时请求可能数百个,这里通过Semaphore信号量来控制每次发出请求的最大数,该部分的讨论不再本话题之类)。
问题出来了,那么如何知道所有的网络请求全部完成了,可以进行下一步算法分析呢?答案是利用前面讲的ManualResetEvent来处理;于是有下面的写法
//针对每个线程 绑定初始化一个ManualResetEvent实例
ManualResetEvent doneEvent =
new
ManualResetEvent(
false
);
//通过ThreadPool.QueueUserWorkItem(网络请求方法HttpRequest,doneEvent ) 来开启多线程
//将等待事件一一加入事件列表
List<ManualResetEvent> listEvent =
new
List<ManualResetEvent>();
for
(
int
i=0;i<请求线程数;i++){
listEvent.Add(doneEvent);
}
//主线程等待网络请求全部完成
WaitHandle.WaitAll(listEvent.ToArray());
//....接下去的算法分析
//在网络请求方法HttpRequest的子线程中调用
doneEvent.Set();
//通知主线程 本网络请求已经完成
运行好像没有问题,程序按原定计划执行;但是当线程数大于64个之后抛出异常
WaitHandles must be less than or equal to 64
原来WaitHandle.WaitAll(listEvent.ToArray()); 这里listEvent线程数不能超过64个
以前解决方法:
下面是吴建飞以前的方案:既然WaitHandle.WaitAll方法只能唤醒64个ManualResetEvent对象,那么就采用
List<List<ManualResetEvent>> _listLocEventList =
new
List<List<ManualResetEvent>>();
采用这种复杂集合;集合的每个元素也是一个集合(内部每个集合包含最大64个ManualResetEvent对象);和上面一样 把每个线程相关的ManualResetEvent对象添加到该集合;
//主线程等待网络请求全部完成
foreach
(List<ManualResetEvent> listEvent
in
_listLocEventList)
{
WaitHandle.WaitAll(listEvent.ToArray());
}
该方案运用起来比较复杂,而且会导致创建大量的ManualResetEvent对象;
现在的设计目标是这种对文件的分析是多任务同时进行的,也就是说会产生的ManualResetEvent对象List<List<ManualResetEvent>>.Size() * 任务数(N个文件上传)
改进的解决方法:
原理:封装一个ManualResetEvent对象,一个计数器current,提供SetOne和WaitAll方法;
主线程调用WaitAll方法使ManualResetEvent对象等待唤醒信号;
各个子线程调用setOne方法 ,setOne每执行一次current减1,直到current等于0时表示所有子线程执行完毕 ,调用ManualResetEvent的set方法,这时主线程可以执行WaitAll之后的步骤。
目标:减少ManualResetEvent对象的大量产生和使用的简单性。
在这里我写了个封装类:
/********************************************************************************
* Copyright © 2001 - 2010Comit. All Rights Reserved.
* 文件:MutipleThreadResetEvent.cs
* 作者:杨柳
* 日期:2010年11月13日
* 描述:封装 ManualResetEvent ,该类允许一次等待N(N>64)个事件执行完毕
*
* 解决问题:WaitHandle.WaitAll(evetlist)方法最大只能等待64个ManualResetEvent事件
* *********************************************************************************/
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading;
namespace
TestMutipleThreadRestEvent
{
/// <summary>
/// 封装ManualResetEvent
/// </summary>
public
class
MutipleThreadResetEvent : IDisposable
{
private
readonly
ManualResetEvent done;
private
readonly
int
total;
private
long
current;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="total">需要等待执行的线程总数</param>
public
MutipleThreadResetEvent(
int
total)
{
this
.total = total;
current = total;
done =
new
ManualResetEvent(
false
);
}
/// <summary>
/// 唤醒一个等待的线程
/// </summary>
public
void
SetOne()
{
// Interlocked 原子操作类 ,此处将计数器减1
if
(Interlocked.Decrement(
ref
current) == 0)
{
//当所以等待线程执行完毕时,唤醒等待的线程
done.Set();
}
}
/// <summary>
/// 等待所以线程执行完毕
/// </summary>
public
void
WaitAll()
{
done.WaitOne();
}
/// <summary>
/// 释放对象占用的空间
/// </summary>
public
void
Dispose()
{
((IDisposable)done).Dispose();
}
}
}
注释写的很清楚了:本质就是只通过1个ManualResetEvent 对象就可以实现同步N(N可以大于64)个线程
下面是测试用例:
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading;
namespace
TestMutipleThreadRestEvent
{
/// <summary>
/// 测试MutipleThreadResetEvent
/// </summary>
class
Program
{
static
int
i = 0;
/// <summary>
/// 主方法
/// </summary>
/// <param name="args">参数</param>
static
void
Main(
string
[] args)
{
//假设有100个请求线程
int
num = 100;
//使用 MutipleThreadResetEvent
using
(
var
countdown =
new
MutipleThreadResetEvent(num))
{
for
(
int
i=0;i<num;i++)
{
//开启N个线程,传递MutipleThreadResetEvent对象给子线程
ThreadPool.QueueUserWorkItem(MyHttpRequest, countdown);
}
//等待所有线程执行完毕
countdown.WaitAll();
}
Console.WriteLine(
"所有的网络请求以及完毕,可以继续下面的分析..."
);
Console.ReadKey();
}
/// <summary>
/// 假设的网络请求
/// </summary>
/// <param name="state">参数</param>
private
static
void
MyHttpRequest(
object
state)
{
// Thread.Sleep(1000);
Console.WriteLine(String.Format(
"哈哈:{0}"
,++i));
MutipleThreadResetEvent countdown = state
as
MutipleThreadResetEvent;
//发送信号量 本线程执行完毕
countdown.SetOne();
}
}
}
输出:
… 省略 ...
从结果上看线程执行的完成的时间顺序是不固定的;并且只有在所有100个网络请求任务完成后,才显示可以继续下面的分析。
与上面的方案是一样的效果,但是本方案使用非常简单,出错的概念小,免去了创建大量 ManualResetEvent 对象的烦恼
该解决方案可以适用与.net framework 2.0 以上的运行时。
tips:在.net framework 4.0 中有一个CountdownEvent对象可以实现类似的功能;
不过目前公司大多数项目运行时还是基于.net framework 2.0 和 3.5
CountdownEvent
- .NET Framework 4.5
System.Threading.CountdownEvent是一个同步基元,它在收到一定次数的信号之后,将会解除对其等待线程的锁定。CountdownEvent专门用于以下情况:您必须使用ManualResetEvent 或ManualResetEventSlim,并且必须在用信号通知事件之前手动递减一个变量。例如,在分叉/联接方案中,您可以只创建一个信号计数为 5 的 CountdownEvent,然后在线程池上启动五个工作项,并且让每个工作项在完成时调用Signal。每次调用Signal 时,信号计数都会递减 1。在主线程上,对Wait 的调用将会阻塞,直至信号计数为零。
======================================
http://www.cnblogs.com/charley_yang/archive/2010/11/13/1876626.html
http://technet.microsoft.com/zh-CN/library/dd997365(v=vs.100)
- C#多线程之三:解决多线程编程中大并发数等待唤醒的问题
- C#多线程之三:解决多线程编程中大并发数等待唤醒的问题
- 解决多线程编程中大并发数等待唤醒的问题
- 多线程之等待唤醒机制
- 并发学习之:多线程编程中条件变量和虚假唤醒的讨论
- 并发学习之:多线程编程中条件变量和虚假唤醒的讨论
- 多线程的等待唤醒机制
- 多线程间的通讯之等待唤醒机制
- 【Qt多线程之线程的等待和唤醒】QWaitCondition
- java多线程通信之等待唤醒机制
- 多线程-生产者消费者之等待唤醒机制
- java多线程之等待/唤醒机制
- 多线程等待唤醒机制
- 多线程-等待唤醒机制
- Java 多线程间的通信 等待唤醒
- 【并发】多线程编程中条件变量和虚假唤醒的讨论
- 解决多线程并发问题
- 黑马程序员-多线程部分(三.等待唤醒机制)
- 我的第一篇博客
- 基于矩阵分解的隐因子模型
- 浅谈变量的存储位置
- 如何让新工程在pycharm里快速运行
- C语言函数---S
- C#多线程之三:解决多线程编程中大并发数等待唤醒的问题
- 伟大的、优美的Linq (1)
- 手机游戏消息推送使用浅析
- Android 打造任意层级树形控件 考验你的数据结构和设计
- 网站中添加icon
- Google Reader为什么会关闭
- Android 时间戳的作用
- git 的使用(7)-标签管理
- 隐藏输入密码