详解c# 并行计算

并行计算部分
沿用微软的写法,System.Threading.Tasks.::.Parallel类,提供对并行循环和区域的支持。 我们会用到的方法有For,ForEach,Invoke。

一、简单使用
首先我们初始化一个List用于循环,这里我们循环10次。(后面的代码都会按这个标准进行循环)

    Program.Data = new List<int>();
    for (int i = 0; i < 10; i++)
    {
        Data.Add(i);
    }

下面我们定义4个方法,分别为for,foreach,并行For,并行ForEach。并测试他们的运行时长。

/// <summary>
/// 是否显示执行过程
/// </summary>
public bool ShowProcessExecution = false;
/// <summary>
/// 这是普通循环for
/// </summary>
private void Demo1()
{
    List<int> data = Program.Data;
    DateTime dt1 = DateTime.Now;
    for (int i = 0; i < data.Count; i++)
    {
        Thread.Sleep(500);
        if (ShowProcessExecution)
            Console.WriteLine(data[i]);
    }
    DateTime dt2 = DateTime.Now;
    Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
}
/// <summary>
/// 这是普通循环foreach
/// </summary>
private void Demo2()
{
    List<int> data = Program.Data;
    DateTime dt1 = DateTime.Now;
    foreach (var i in data)
    {
        Thread.Sleep(500);
        if (ShowProcessExecution)
            Console.WriteLine(i);
    }
    DateTime dt2 = DateTime.Now;
    Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
}
/// <summary>
/// 这是并行计算For
/// </summary>
private void Demo3()
{
    List<int> data = Program.Data;
    DateTime dt1 = DateTime.Now;
    Parallel.For(0, data.Count, (i) =>
    {
        Thread.Sleep(500);
        if (ShowProcessExecution)
            Console.WriteLine(data[i]);
    });
    DateTime dt2 = DateTime.Now;
    Console.WriteLine("并行运算For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
}
/// <summary>
/// 这是并行计算ForEach
/// </summary>
private void Demo4()
{
    List<int> data = Program.Data;
    DateTime dt1 = DateTime.Now;
    Parallel.ForEach(data, (i) =>
    {
        Thread.Sleep(500);
        if (ShowProcessExecution)
            Console.WriteLine(i);
    });
    DateTime dt2 = DateTime.Now;
    Console.WriteLine("并行运算ForEach运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
}

下面是运行结果:

在这里插入图片描述

这里我们可以看出并行循环在执行效率上的优势了。

结论1:在对一个数组内的每一个项做单独处理时,完全可以选择并行循环的方式来提升执行效率。

原理1:并行计算的线程开启是缓步开启的,线程数量1,2,4,8缓步提升。(不详,PLinq最多64个线程,可能这也是64)

二、 并行循环的中断和跳出
当在进行循环时,偶尔会需要中断循环或跳出循环。下面是两种跳出循环的方法Stop和Break,LoopState是循环状态的参数。

 /// <summary>
    /// 中断Stop
    /// </summary>
    private void Demo5()
    {
        List<int> data = Program.Data;
        Parallel.For(0, data.Count, (i, LoopState) =>
        {
            if (data[i] > 5)
                LoopState.Stop();
            Thread.Sleep(500);
            Console.WriteLine(data[i]);
        });
        Console.WriteLine("Stop执行结束。");
    }
    /// <summary>
    /// 中断Break
    /// </summary>
    private void Demo6()
    {
        List<int> data = Program.Data;
        Parallel.ForEach(data, (i, LoopState) =>
        {
            if (i > 5)
                LoopState.Break();
            Thread.Sleep(500);
            Console.WriteLine(i);
        });
        Console.WriteLine("Break执行结束。");
    }
    执行结果如下:

在这里插入图片描述

结论2:使用Stop会立即停止循环,使用Break会执行完毕所有符合条件的项。

三、并行循环中为数组/集合添加项
上面的应用场景其实并不是非常多见,毕竟只是为了遍历一个数组内的资源,我们更多的时候是为了遍历资源,找到我们所需要的。那么请继续看。

下面是我们一般会想到的写法:

private void Demo7()
{
    List<int> data = new List<int>();
    Parallel.For(0, Program.Data.Count, (i) =>
    {
        if (Program.Data[i] % 2 == 0)
            data.Add(Program.Data[i]);
    });
    Console.WriteLine("执行完成For.");
}
private void Demo8()
{
    List<int> data = new List<int>();
    Parallel.ForEach(Program.Data, (i) =>
    {
        if (Program.Data[i] % 2 == 0)
            data.Add(Program.Data[i]);
    });
    Console.WriteLine("执行完成ForEach.");
}

看起来应该是没有问题的,但是我们多次运行后会发现,偶尔会出现错误如下:

在这里插入图片描述

这是因为List是非线程安全的类,我们需要使用System.Collections.Concurrent命名空间下的类型来用于并行循环体内。

在这里插入图片描述

那么我们上面的代码可以修改为,加了了ConcurrentQueue和ConcurrentStack的最基本的操作。

/// <summary>
/// 并行循环操作集合类,集合内只取5个对象
/// </summary>
private void Demo7()
{
    ConcurrentQueue<int> data = new ConcurrentQueue<int>();
    Parallel.For(0, Program.Data.Count, (i) =>
    {
        if (Program.Data[i] % 2 == 0)
            data.Enqueue(Program.Data[i]);//将对象加入到队列末尾
    });
    int R;
    while (data.TryDequeue(out R))//返回队列中开始处的对象
    {
        Console.WriteLine(R);
    }
    Console.WriteLine("执行完成For.");
}
/// <summary>
/// 并行循环操作集合类
/// </summary>
private void Demo8()
{
    ConcurrentStack<int> data = new ConcurrentStack<int>();
    Parallel.ForEach(Program.Data, (i) =>
    {
        if (Program.Data[i] % 2 == 0)
            data.Push(Program.Data[i]);//将对象压入栈中
    });
    int R;
    while (data.TryPop(out R))//弹出栈顶对象
    {
        Console.WriteLine(R);
    }
    Console.WriteLine("执行完成ForEach.");
}

ok,这里返回一个序列的问题也解决了。

结论3:在并行循环内重复操作的对象,必须要是thread-safe(线程安全)的。集合类的线程安全对象全部在System.Collections.Concurrent命名空间下。

四、返回集合运算结果/含有局部变量的并行循环
使用循环的时候经常也会用到迭代,那么在c#教程并行循环中叫做 含有局部变量的循环 。下面的代码中详细的解释,这里就不啰嗦了。

/// <summary>
/// 具有线程局部变量的For循环
/// </summary>
private void Demo9()
{
    List<int> data = Program.Data;
    long total = 0;
    //这里定义返回值为long类型方便下面各个参数的解释
    Parallel.For<long>(0,           // For循环的起点
        data.Count,                 // For循环的终点
        () => 0,                    // 初始化局部变量的方法(long),既为下面的subtotal的初值
        (i, LoopState, subtotal) => // 为每个迭代调用一次的委托,i是当前索引,LoopState是循环状态,subtotal为局部变量名
        {
            subtotal += data[i];    // 修改局部变量
            return subtotal;        // 传递参数给下一个迭代
        },
        (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
        );
    Console.WriteLine(total);
}
/// <summary>
/// 具有线程局部变量的ForEach循环
/// </summary>
private void Demo10()
{
    List<int> data = Program.Data;
    long total = 0;
    Parallel.ForEach<int, long>(data, // 要循环的集合对象
        () => 0,                      // 初始化局部变量的方法(long),既为下面的subtotal的初值
        (i, LoopState, subtotal) =>   // 为每个迭代调用一次的委托,i是当前元素,LoopState是循环状态,subtotal为局部变量名
        {
            subtotal += i;            // 修改局部变量
            return subtotal;          // 传递参数给下一个迭代
        },
        (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
        );
    Console.WriteLine(total);
}

结论4:并行循环中的迭代,确实很伤人。代码太难理解了。

五、PLinq(Linq的并行计算)
上面介绍完了For和ForEach的并行计算盛宴,微软也没忘记在Linq中加入并行计算。下面介绍Linq中的并行计算。

4.0中在System.Linq命名空间下加入了下面几个新的类:

在这里插入图片描述

原理2:PLinq最多会开启64个线程

原理3:PLinq会自己判断是否可以进行并行计算,如果不行则会以顺序模式运行。

原理4:PLinq会在昂贵的并行算法或成本较低的顺序算法之间进行选择,默认情况下它选择顺序算法。

在ParallelEnumerable中提供的并行化的方法

在这里插入图片描述

下面是PLinq的简单代码

/// <summary>
/// PLinq简介
/// </summary>
private void Demo11()
{
    var source = Enumerable.Range(1, 10000);
    //查询结果按source中的顺序排序
    var evenNums = from num in source.AsParallel().AsOrdered()
               where num % 2 == 0
               select num;
    //ForAll的使用
    ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
    var query = from num in source.AsParallel()
                where num % 10 == 0
                select num;
    query.ForAll((e) => concurrentBag.Add(e * e));
}

上面代码中使用了ForAll,ForAll和foreach的区别如下:
在这里插入图片描述

以上就是详解c# 并行计算的详细内容

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值