• .Net 4.0并行库实用性演练


      前面说在练习Parallel时,发现另有乾坤,是这样的代码:

    代码
    static IEnumerable<Person> testFill()
    {
    var list
    =new List<Person>(9);

    Enumerable.Range(
    1, 99999).ToList().ForEach(n =>
    {
    var name
    ="Person"+ n %9;
    list.Add(
    new Person { Id = n, Name = name });
    });
    Console.WriteLine(
    "Person's count is {0}", list.Count);
    return list;
    }

    static IEnumerable<Person> testFillParallel()
    {
    var list
    =new List<Person>(9);

    Enumerable.Range(
    1, 99999).AsParallel().ForAll(n =>
    {
    var name
    ="Person"+ n %9;
    list.Add(
    new Person { Id = n, Name = name });
    });
    Console.WriteLine(
    "Person's count is {0}", list.Count);
    return list;
    }

    class Person
    {
    internalint Id { get; set; }
    internalstring Name { get; set; }
    }

      试验结果如下(单位ms):

     次数

     1

     2

     3

     4

     Fill 方法

     37

     27

     26

     26

     FillParallel 方法

     43

     20

     19

     20

      这个结果有点奇妙的。第一次多线程居然还不如单线程快,和上文例子比较一下,有点明白了。稍微改了下代码,在Add语句前加了个Thread.Sleep(1),并把 List<Person>集合元素减为999,试了一次,结果如下(单位ms):

     次数

     1

     2

     3

     4

     Fill 方法

     1012

     998

     998

     999

     FillParallel 方法

     547

     504

     504

     504

      多个线程协同工作时,分配任务本身有开销,要是分配的开销比任务本身还大,多线程就没有意义了。就比如你交待别人做某件事,要是交待的功夫比自己做还长,还不如自己做。不过从结果也可以看出一个辩证关系,从长远打算,第一次让别人熟悉业务,付出点培训成本,执行完一次后,以后就轻松多了,速度提高了一倍。如果这里Sleep一下,模拟长一点的单次处理过程,一开始多线程的优势就会非常明显。

      FillParallel方法,大家觉得有没有其它问题呢?想必一般人都能看出,这里有最初级的线程安全问题。没看出的应该是刚学.Net各种集合的初学者,线程安全对他们还只是个太虚幻境。不过借助这个Parallel,就可以轻松神游幻境。把FillParallel方法循环一百次执行,会发现返回结果本来应该有999个元素,输出的却显示却结果经常少十几二十个。如果创建List时赋的容量不够,在List扩容时,还可能引发异常。一般是像下图这样(不过一百次都是999也不是不可能,要看你的RP了):

      应提醒一点的是,试验要在Release编译模式下运行,不然看不到线程安全问题,并行执行的效率提升得也很有限。我用的电脑都是双核,不知道在单核电脑的运行情况如何,可能有一定区别。

      接着我改下逻辑,增加了一个是否Person存在重名的判断,变成:

    代码
    static IEnumerable<Person> testFillParallel()
    {
    var list
    =new List<Person>(9);

    Enumerable.Range(
    1, 999).AsParallel().ForAll(n =>
    {
    var name
    ="Person"+ n %9;
    if (list.Count(p => p.Name == name) <1) list.Add(new Person { Id = n, Name = name });
    });
    Console.WriteLine(
    "Person's count is {0}", list.Count);
    return list;
    }

      RP不管用了,执行几次,必抛异常:System.InvalidOperationException: Collection was modified; enumeration operation may no execute.

      一个线程在枚举集合元素,这时必须保证集合不被其它线程修改,怎么办呢?以前,就知道用锁,现在据说有了线程安全的集合类,在System.Collections.Concurrent命名空间下,有ConcurrentDictionary, ConcurrentQueue, ConcurrentStack,就是没有ConcurrentList。费了半天,才发现与List对应的应该是BlockingCollection。

      把集合定义换成: var list = new BlockingCollection<Person>(9); 只见刷刷刷,哪怕执行几万次都可以一路跑完了。

      不过这样做,还是会发现问题,不知大家看出了吗?

  • 相关阅读:
    git 提交
    Git提交出现 Everything up-to-date
    vscode 记住git用户密码
    git 将本地项目关联到远程仓库
    ubuntu19.04 安装workbench
    修改vscode终端样式
    python3练习
    C#实现MJPEG服务器
    在WPF中使用AForge控件
    FtpWebRequest.UsePassive属性:设置FTP工作模式
  • 原文地址:https://www.cnblogs.com/XmNotes/p/1821627.html
Copyright © 2020-2023  润新知