并行开发-Paraller

Posted gnailgnepgnaw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并行开发-Paraller相关的知识,希望对你有一定的参考价值。

并行开发的概念

并行开发要做的事情就是将任务分摊给硬件线程去并行执行来达到负载和加速,传统的代码都是串行的,就一个主线程,当我们为了实现加速而开了很多工作线程,这些工作线程就是软件线程

Parallel的使用

Parallel类是对线程的抽象,位于System.Threading.Tasks名称空间下,提供了任务和数据并行性.在Parallel下有三个常用的方法Invoke,For和ForEach,其中Parallel.Invoke用于任务并行性,Parallel.ForEach用于数据并行性

Parallel.Invoke

如果多个任务应并行运行,就可以使用Parallel.Invoke()方法,可以最简单,最简洁的将串行的代码并行化

简单应用

class ThreadTest
{
    static void Main(string[] args)
    {
        var watch = Stopwatch.StartNew();
        watch.Start();
        Run1();
        Run2();
        Run3();
        watch.Stop();
        Console.WriteLine("串行开发,总耗时{0}", watch.ElapsedMilliseconds);

        watch.Restart();

        Parallel.Invoke(Run1, Run2, Run3);
        watch.Stop();
        Console.WriteLine("并行开发,总耗时{0}", watch.ElapsedMilliseconds);
        Console.ReadKey();
    }
    static void Run1()
    {
        Console.WriteLine("Run1,我需要1s");
        Thread.Sleep(1000);
    }
    static void Run2()
    {
        Console.WriteLine("Run2,我需要3s");
        Thread.Sleep(3000);
    }
    static void Run3()
    {
        Console.WriteLine("Run3,我需要4s");
        Thread.Sleep(4000);
    }
}

 技术图片

主程序启动时,先顺序调用Run1(),Run()2,Run3()方法,这是串行的,而后使用Parallel.Invoke()将三个方法并行调用,可见耗时是有明显下降的

执行顺序

static void Main(string[] args)
{
    Console.WriteLine("主线程启动,线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
    Parallel.Invoke(() => Run1("task1"), () => Run2("task2"), () => Run3("task3"));
    Console.WriteLine("主线程结束,线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
    Console.ReadKey();
}
static void Run1(string taskName)
{
    Console.WriteLine("任务名:{0}线程ID:{1}", taskName, Thread.CurrentThread.ManagedThreadId);
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("a");
    }
}
static void Run2(string taskName)
{
    Console.WriteLine("任务名:{0}线程ID:{1}", taskName, Thread.CurrentThread.ManagedThreadId);
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("b");
    }
}
static void Run3(string taskName)
{
    Console.WriteLine("任务名:{0}线程ID:{1}", taskName, Thread.CurrentThread.ManagedThreadId);
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("c");
    }
}

技术图片

结果可知:

      1、没有固定顺序,每个Task可能是不同的线程去执行,也可能是相同的

      2、主线程必须等Invoke中的所有方法执行完成后返回才继续向下执行,以后设计并行的时候,要考虑每个Task任务尽可能差不多,如果相差很大,比如一个时间非常长,其他都比较短,这样一个线程可能会影响整个任务的性能。这点非常重要(就是说Invoke会阻塞主线程)

异常产生

static void Main(string[] args)
{
    Console.WriteLine("主线程启动,线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
    try
    {
        Parallel.Invoke(() => Run1("task1"), () =>
        {
            throw new Exception("出现异常");
        }, () => Run2("task2"), () => Run3("task3"));
    }
    catch (AggregateException ex)
    {
        foreach (var item in ex.InnerExceptions)
        {
            Console.WriteLine(item);
        }
    }
    Console.WriteLine("主线程结束,线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
    Console.ReadKey();
}

技术图片

Invoke方法中调用了一个产生异常的方法,但是结果发现异常并不会影响其它方法及主线程的执行

重载方法ParallelOptions 的使用

主要理解两个参数:

     CancellationToken:控制线程的取消
     MaxDegreeOfParallelism :设置最大的线程数,有时候可能会跑遍所有的内核,为了提高其他应用程序的稳定性,就要限制参与的内核

Parallel.For

串行代码中也有一个for,但是那个for并没有用到多核,而Paraller.For它会在底层根据硬件线程的运行状况来充分的使用所有的可利用的硬件线程

static void Main(string[] args)
{
    for (int i = 0; i < 3; i++)
    {
        ConcurrentBag<int> bag = new ConcurrentBag<int>();
        var watch = Stopwatch.StartNew();
        watch.Start();

        for (int j = 0; j < 20000000; j++)
        {
            bag.Add(i);
        }
        watch.Stop();
        Console.WriteLine("串行添加,总数20000000,耗时{0}", watch.ElapsedMilliseconds);
        GC.Collect();
        watch.Restart();
        Parallel.For(0, 20000000, j =>
        {
            bag.Add(j);
        });
        watch.Stop();
        Console.WriteLine("并行添加,总数20000000,耗时{0}", watch.ElapsedMilliseconds);
        Console.WriteLine("***********************************");
        GC.Collect();
    }
    Console.ReadKey();
}

向一个线程安全的集合插入数据,使用串行的for耗时与使用并行的Parallel.For差异

技术图片

Parallel.For提前中断

 

Parallel.ForEach

ForEach的特点在于可以将数据进行分区,每一个小区内实现串行计算,分区采用Partitioner.Create实现

Parallel.For/for   Parallel.ForEach/foreach性能比较

 

以上是关于并行开发-Paraller的主要内容,如果未能解决你的问题,请参考以下文章

并行循环

如何在 python 中并行化以下代码片段?

oracle并行相关的parallel_max_server参数

[工作积累] UE4 并行渲染的同步 - Sync between FParallelCommandListSet & FRHICommandListImmediate calls(代码片段

ViewPager的两个片段并行运行

Node.js异步库async