zoukankan      html  css  js  c++  java
  • Net4.0 Parallel编程(二)Data Parallelism 中_转

    上篇文章中看过了使用Parrallel.For、Parael.Foreach在效率上给我们带来的提高。本文就来如何终止循环、线程局部变量 进行说明。

    Thread-Local Variables

    首先我们来看下线程局部变量,是的我们也许一直在想我们如何去定义一个线程局部变量呢。先看段顺序执行的代码:

    01.[TestMethod()]
    02.public void NormalSequenceTest()
    03.{
    04.    int[] nums = Enumerable.Range(0, 1000000).ToArray();
    05.    long total = 0;
    06.    for (int i = 0; i < nums.Length;i++ )
    07.    {
    08.        total += nums[i];
    09.    }
    10.    Console.WriteLine("The total is {0}", total);
    11.}

    执行结果:

    image

    我们再来看这段代码:

    01.[TestMethod()]
    02.public void NormalParallelTest()
    03.{
    04.    int[] nums = Enumerable.Range(0, 1000000).ToArray();
    05.    long total = 0;
    06.    Parallel.For(0,nums.Length,i=>
    07.    {
    08.        total += nums[i];
    09.    });
    10.    Console.WriteLine("The total is {0}", total);
    11.}

    执行结果:

    image

    再运行下:

    image

    也许我们会感到很奇怪为什么会这样呢,其实我们想想就可以明白了,total变量是公共的,而我们的程序是多个线程的加,而多个线程之间是不能把数据共享的。其实我们需要的是在每个线程中计算出一个和值,然后再进行累加。我们来看看线程局部变量:

    01.[TestMethod()]
    02.public void ThreadLocalTest()
    03.{
    04.    int[] nums = Enumerable.Range(0, 1000000).ToArray();
    05.    long total = 0;
    06.    Parallel.For<long>(0, nums.Length, () => 0, (j, loop, subtotal) =>
    07.    {
    08.        subtotal += nums[j];
    09.        return subtotal;
    10.    },
    11.        (x) => Interlocked.Add(ref total, x)
    12.    );
    13.      
    14.    Console.WriteLine("The total is {0}", total);
    15.}

    我们再看下执行结果:

     image

    下面说下泛型方法Parallel.For<T>方法,方法的原型:

    1.public static ParallelLoopResult For<TLocal>(int fromInclusive, int toExclusive, Func<TLocal> localInit, Func<int, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);

    TLocal:线程变量的类型;第一个、第二个参数就不必多说了,就是其实值跟结束值。

    localInit:每个线程的线程局部变量初始值的设置;

    body:每次循环执行的方法,其中方法的最后一个参数就是线程局部变量;

    localFinally:每个线程之后执行的方法。

    相信这样解释后就能明白了为什么需要线程局部变量了,也明白如何使用线程局部变量了。我们再看看在Parallel.Foreach<T>中如何使用:

    01.[TestMethod()]
    02.public void ForeachThreadLocalTest()
    03.{
    04.    int[] nums = Enumerable.Range(0, 1000000).ToArray();
    05.    long total = 0;
    06.    Parallel.ForEach<int,long>(nums,()=>0,(member,loopState,subTotal)=>
    07.        {
    08.            subTotal += member;
    09.            return subTotal;
    10.        },
    11.            (perLocal)=> Interlocked.Add(ref total,perLocal)
    12.       );
    13.    Console.WriteLine("The total is {0}", total);
    14.}

    要注意的是,我们必须要使用ForEach<TSource, TLocal>,因为第一个参数表示的是迭代源的类型,第二个表示的是线程局部变量的类型,其方法的参数跟For是差不多的。

    Break、Stop

    首先我们可以看到在Parallel.For的一个重载方法中:

    1.public static ParallelLoopResult For(int fromInclusive, int toExclusive, Action<int, ParallelLoopState> body);

    在委托的最后一个参数类型为ParallelLoopState,而ParallelLoopState里面提供给我们两个方法:Break、Stop来终止迭代,而Break跟Stop的区别是什么呢?我们来看两段代码:

    01.private void StopLoop()
    02.{
    03.    var Stack = new ConcurrentStack<string>();
    04.    Parallel.For(0, 10000, (i, loopState) =>
    05.    {
    06.        if (i < 1000)
    07.            Stack.Push(i.ToString());
    08.        else
    09.        {
    10.            loopState.Stop();
    11.            return;
    12.        }
    13.    });
    14.    Console.WriteLine("Stop Loop Info:\n elements count:{0}", Stack.Count);    
    15.}
    16.private void BreakLoop()
    17.{
    18.    var Stack = new ConcurrentStack<string>();
    19.    var stringList = this.ConstructString(10000);
    20.    Parallel.For(0, stringList.Count, (i, loopState) =>
    21.    {
    22.        Stack.Push(stringList[i]);
    23.        if (stringList[i].Contains("999"))
    24.        {
    25.            loopState.Break();
    26.        }
    27.    });
    28.    Console.WriteLine("Stop Loop Info:\n elements count:{0}", Stack.Count);
    29.}
    30.private List<string> ConstructString(int number)
    31.{
    32.    var stringList = new List<string>();
    33.    Parallel.For(0, number - 1, i =>
    34.    {
    35.        stringList.Add(i.ToString());
    36.    });
    37.    return stringList;
    38.}

    测试方法:

    1.[TestMethod()]
    2.public void LoopTest()
    3.{
    4.    StopLoop();
    5.    BreakLoop();
    6.}

    来看看运行结果吧:

    image

    其实这个例子只是想告诉大家,为什么第一个用Stop,第二个用Break。原因是:第一个例子中我只关心的是循环的迭代变量i的值,我只要1000个字符串,而不去管这1000个字符串是什么东西。所以当达到1000时,我们就立刻停止所有的迭代包括其他线程上的。而第二个方法中我们是判断的源中的某个索引值,这个时候有可能较早的元素还未处理。

    其实在我们调用过Stop或者Break方法后,循环上的其他的线程可能还会运行一段时间,其实我们可以通过IsStopped属性来判断循环是在其他线程上停止。Foreach中的使用就不再看了,跟For是一样的。

    总结

    在本文中,主要介绍了如何停止循环、使用线程局部变量。在里面我们看到了我们在使用并行开发时,有很多东西是需要我们去注意的。在下文中将介绍下异常处理、取消循环等话题。

  • 相关阅读:
    Mongo连接远程数据库
    将MongoDB服务器设置成Windows启动服务(win10)
    关于php初学者的理解!请大家浏览并指出不足!谢谢!
    python+selenium初学者常见问题处理
    极客时间测试专栏阅读总结——软件测试总体方案
    pytest 一.安装和使用入门
    软件测试工程师——你不仅仅应该会点点点
    测试电梯、杯子、桌子、洗衣机的方法
    软件测试面试-软件测试宝典
    支付测试场景
  • 原文地址:https://www.cnblogs.com/timy/p/1811159.html
Copyright © 2011-2022 走看看