哈哈,突然发现google真的什么都能查到--我现在就想大笑。刚刚看了一段STAX脚本,里面对paralleliterate的解释为:“相当于java里的for,但是并行循环”,我就在想,并行执行测试任务,除了STAX这么难用的东西之外肯定还有什么可以做到,顺手在搜索框中打了一个并行循环,居然搜到这么多。
转自 : http://www.cnblogs.com/yujiasw/archive/2007/12/20/1006760.html
.NET 并行扩展(ParallelFX) 试用(上)
单核处理器单靠提高频率来提升性能变得越来越困难,多核处理器已经是大势所趋。目前绝大部分新买的电脑都已经是双核的了,今后还会有更多的核心。
 与硬件的多核形成对比的是,软件开发领域,还没有成熟的技术让我们方便的使用多核所带来的性能提升,大多数的执行过程仍然是单线程的,因为写多线程的应用很困难,除非涉及到大量的并发IO,否则一般不会使用多线程(至少我是这样,因为我..懒)。
 值得欣喜的是,微软的一批牛人也在为此努力的工作,旨在开发一个并行库,使得更加容易的编写能够自动使用多核处理器的托管代码,这就是 TPL(The Task Parallel Library)。微软于12月5日发布了其CTP版本,可以从这里下面的链接下载,里面还有文档和示例。
http://www.microsoft.com/downloads/details.aspx?FamilyID=e848dc1d-5be3-4941-8705-024bc7f180ba&displaylang=en
 迫不及待的把它Down了下来,装上,然后开始试用。
 默认安装目录为C:\Program Files\Microsoft Parallel Extensions Dec07 CTP,在目录下有一个DLL文件System.Threading.dll,这就我们期待的TPL,还有两个文件夹,一个是文档,一个是示例。
 System.Threading.dll中有一个System.Threading.Parallel类,有四个用于并行计算的方法:For,For<>,ForEach<>,Do
 首先,我们设计一个计算: 复制内容到剪贴板 
代码:
 private static void Normal()
 {
 //获取容量为1,000,000的整数列表
 List<int> list = GetIntList();
 double result = 0;
 for (int i = 0; i < list.Count; i++)
 {
 for (int n = 0; n < 100; n++)
 {
 result += Math.Sqrt(list) * Math.Sin(list);
 }
 }
 Console.WriteLine("result=" + result);
 }我们使用Parallel.For<>,来改造这个计算。For<>方法有四个重载,最复杂的一个是:
 public static void For<TLocal>(int fromInclusive, int toExclusive, int step, Func<TLocal> threadLocalSelector, Action<int, ParallelState<TLocal>> body, Action<TLocal> threadLocalCleanup);
 fromInclusive:起始值
 toExclusive:结束值
 step:步长 
 threadLocalSelector:线程初始化方法, 
 body:每一步要执行的任务,
 threadLocalCleanup:线程结束后要执行的方法。
 其他的重载可以看作是对某些参数使用默认值后对这个函数的调用,我们用的便是其默认步长为1的重载,改造后的计算: 复制内容到剪贴板 
代码:
 private static void ParallelCalculate()
 {
 double result = 0;
 object syncObj = new object();
 //获取容量为1,000,000的整数列表
 List<int> list = GetIntList();
 //运算从0到list.Count
 //() => 0:线程状态初值为零,这个线程状态会在执行体中作为参数传入
 //(index, ps) =>:index是 从0到list.Count的值,ps是线程状态,在线程结束前不会重置
 //ps.ThreadLocalState:用我们的传入第三个函数参数初始化的值,可以用来保存线程的中间计算结果
 //threadResult:线程结束时传入的参数,和ps.ThreadLocalState为同一个值
 Parallel.For<double>(0, list.Count, () => 0, (index, ps) =>
 {
 for (int n = 0; n < 100; n++)
 {
 //把结果保存在线程状态里
 ps.ThreadLocalState += Math.Sqrt(list[index]) * Math.Sin(list[index]);
 }
 }, threadResult =>
 {
 //线程结束时,将线程计算的结果保存到最终结果中
 lock (syncObj)
 {
 result += threadResult;
 }
 });
 Console.WriteLine("result=" + result);
 }来做个性能测试:(环境为:Vista Enterprise,Pentium D 双核 3.4G,1G 内存) 复制内容到剪贴板 
代码:
 DateTime startTime = DateTime.Now;
 Normal();
 Console.WriteLine(DateTime.Now - startTime);
 startTime = DateTime.Now;
 ParallelCalculate();
 Console.WriteLine(DateTime.Now - startTime);从测试结果可以看到。性能提升还是很明显的:
 result=114917.508227442
 00:00:06.1363260
 result=114917.508226942
 00:00:03.5691075
 注意到,两次计算的结果并不完全一样,而是有一定的误差,考虑到计算两个double类型值在相加时,由于精度的限制,有可能对末位进行四舍五入,从而不同的计算顺序必然造成结果的不同,从这个角度上来说,上面的两个计算结果都正确,只是其误差已经使得他们的末尾几位数已经没有了意义。
 
 我们来做个实验,首先抛出异常 复制内容到剪贴板 
代码:
 private static void ExceptionParallelCalculate()
 {
 double result = 0;
 object syncObj = new object();
 List<int> list = GetIntList();
 int computedCount = 0;
 try
 {
 Parallel.For<double>(0, list.Count, () => 0, (index, ps) =>
 {
 if (index == 100000)
 throw new InvalidOperationException();
 for (int n = 0; n < 100; n++)
 ps.ThreadLocalState += Math.Sqrt(list[index]) * Math.Sin(list[index]);
 computedCount++;
 },
 threadResult =>
 {
 lock (syncObj)
 {
 result += threadResult;
 }
 });
 }
 catch (Exception) { };
 Console.WriteLine("result=" + result);
 Console.WriteLine("computedCount={0}---{1}%", computedCount, computedCount / 10000d);
 }运行结果为:
 result=122427.621705062
 computedCount=999992---99.9992%
 
 虽然在运算进行到10%左右的时候就抛出了一个异常,但是最终结果是它几乎完成了所有的运算。因为抛出异常的那个线程(就是计算数字100000 的那个线程)被分配到的任务只占总任务的很少一部分(每次运行结果可能会不同),抛出异常时,它立刻终止当前任务,执行 threadLocalCleanup,并抛弃任务队列里的剩余任务(被抛弃的应该是7个数字的计算任务,加上抛出异常的一个,一共8个)。其他的未分配任务会被其他的线程执行完毕。
 上例中如果把 throw new InvalidOperationException() 换成 ps.Stop() 那么所有线程都会立即终止,运行结果为:
 
 result=12594.4311232184
 computedCount=100000---10%
 
 这次我们尝试阻塞其中的一部分线程: 复制内容到剪贴板 
代码:
 private static void JoinParallelCalculate()
 {
 double result = 0;
 object syncObj = new object();
 object syncObj2 = new object();
 object syncObj3 = new object();
 List<int> list = GetIntList();
 List<int> threadIDList = new List<int>();
 int computedCount = 0;
 try
 {
 Parallel.For<double>(0, list.Count, () => 0, (index, ps) =>
 {
 if (index > 100000 && index < 100100)
 {
 Thread.CurrentThread.Join(500);
 }
 lock (syncObj)
 {
 if (!threadIDList.Contains(Thread.CurrentThread.ManagedThreadId))
 threadIDList.Add(Thread.CurrentThread.ManagedThreadId);
 }
 for (int n = 0; n < 100; n++)
 ps.ThreadLocalState += Math.Sqrt(list[index]) * Math.Sin(list[index]);
 lock (syncObj2)
 {
 computedCount++;
 }
 },
 threadResult =>
 {
 lock (syncObj3)
 {
 result += threadResult;
 }
 });
 }
 catch (Exception) { };
 Console.WriteLine("result=" + result);
 Console.WriteLine("ThreadCount=" + threadIDList.Count);
 Console.WriteLine("computedCount={0}---{1}%", computedCount, computedCount / 10000d);
 }运算结果为:
 result=114917.508241467
 ThreadCount=15
 computedCount=1000000---100%
 
 由于某些线程被阻塞,生成了新的线程。
 如果我们在其中抛出一个异常: 复制内容到剪贴板 
代码:
private static void ExceptionJoinParallelCalculate()
 {
 double result = 0;
 object syncObj = new object();
 object syncObj2 = new object();
 object syncObj3 = new object();
 List<int> list = GetIntList();
 List<int> threadIDList = new List<int>();
 int computedCount = 0;
 try
 {
 Parallel.For<double>(0, list.Count, () => 0, (index, ps) =>
 {
 if (index == 50000)
 throw new InvalidOperationException();
 if (index > 100000 && index < 100100)
 {
 Thread.CurrentThread.Join(500);
 }
 lock (syncObj)
 {
 if (!threadIDList.Contains(Thread.CurrentThread.ManagedThreadId))
 threadIDList.Add(Thread.CurrentThread.ManagedThreadId);
 }
 for (int n = 0; n < 100; n++)
 ps.ThreadLocalState += Math.Sqrt(list[index]) * Math.Sin(list[index]);
 lock (syncObj2)
 {
 computedCount++;
 }
 },
 threadResult =>
 {
 lock (syncObj3)
 {
 result += threadResult;
 }
 });
 }
 catch (Exception) { };
 Console.WriteLine("result=" + result);
 Console.WriteLine("ThreadCount=" + threadIDList.Count);
 Console.WriteLine("computedCount={0}---{1}%", computedCount, computedCount / 10000d);
 }运行结果:
 result=76017.275196288
 ThreadCount=2
 computedCount=999992---99.9992%
 显然,这次因为之前曾有线程抛出异常,即使线程被阻塞了,也没有生成新的线程。
 来个小结:
 1。这个方法并不会对同步执行的线程,也不保证每个任务是按顺序执行的。
 2。线程任务的分配并不是一次完成的,而是一次只分配很少的任务,任务完成后会再分配其他任务,所以每个线程都有一个任务序列,而且都很短。
 3。线程数也是动态分配的,默认情况下,会生成与CPU核心相同数量的线程,但数如果某个线程被阻塞,就会产生新的线程,最终尽量保证正在执行的线程与CPU核心数量相同,每个核心都被充分利用。
 4。如果某个线程抛出了异常,在这之后无论在何种情况下,都不会再生成新的线程,但不会终止已生成的线程。
 5。抛出异常的线程仍然会执行threadLocalCleanup,但是会抛弃其任务队列里未被执行的任务序列。
 5。它会保存任一个线程抛出的异常,并在所有的线程都终止的时候重新抛出,一个线程的异常并不会影响其他线程的执行。
 6。如果要,终止所有的线程,不要使用异常,使用ParallelState的Stop()方法。
posted on 2010-10-13 14:45  Jessica Lu  阅读(140)  评论(0)    收藏  举报