Parallel类

需要先了解下两个概念:“硬件线程”和“软件线程”。

1. 硬件线程

硬件线程又叫做逻辑内核,我们可以在”任务管理器“中查看”性能“标签页,如下图,我们知道有2个硬件线程。


一般情况下,一个物理内核对应一个逻辑内核,比如我这里的2对2。当然如果你的cpu采用的是超线程技术,那么可能就会有4个物理内核对应8个硬件线程

我们要知道并行开发要做的事情就是将任务分摊给这些硬件线程去并行执行来达到负载和加速。

2. 软件线程

    相信这个大家最熟悉了,我们知道传统的代码都是串行的,就一个主线程,当我们为了实现加速而开了很多工作线程,这些工作线程也就是软件线程

一个静态类Parallel简化了在同步状态下的Task的操作。Parallel主要提供了3个有用的方法:For、ForEach、Invoke。

namespace ThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nums = new int[] { 1, 2, 3, 4,5,6,7,8,9,10,11,12 };
            Parallel.For(0, nums.Length, (i) =>
            {
                Console.WriteLine("针对数组索引{0}对应的那个元素{1}的一些工作代码……ThreadId={2}", i, nums[i], Thread.CurrentThread.ManagedThreadId);
            });
            Console.ReadKey();
        }
    }
}


Paraller.for它会在底层根据硬件线程的运行状况来充分的使用所有的可利用的硬件线程,注意这里的Parallel.for的步行是1。

这里我们来演示一下,向一个线程安全的集合插入数据,当然这个集合采用原子性来实现线程同步,比那些重量级的锁机制更加的节省消耗。

namespace ThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int j = 1; j <= 3; j++)
            {
                Console.WriteLine("\n第{0}次比较", j);

                ConcurrentBag<int> bag = new ConcurrentBag<int>();
                var watch = Stopwatch.StartNew();
                watch.Start();
                for (int i = 0; i < 200000; i++)
                {
                    bag.Add(i);
                }
                Console.WriteLine("串行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds);
                GC.Collect();

                bag = new ConcurrentBag<int>();
                watch = Stopwatch.StartNew();
                watch.Start();
                Parallel.For(0, 200000, i =>
                {
                    bag.Add(i);
                });
                Console.WriteLine("并行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds);
                GC.Collect();

            }
        }
    }
}


可以看到,工作代码并不按照数组的索引次序进行遍历。显而易见,这是因为我们的遍历是并行的,不是顺序的。所以这里也可以引出一个小建议:如果我们的输出必须是同步的或者说必须是顺序输出的,则不应使用Parallel的方式。

Foreach方法,主要用于处理泛型集合元素的并行操作,如下:

namespace ThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> nums = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
            Parallel.ForEach(nums, (item) =>
            {
                Console.WriteLine("针对集合元素{0}的一些工作代码……ThreadId={1}", item, Thread.CurrentThread.ManagedThreadId);
            });
            Console.ReadKey();
        }
    }
}


forEach的独到之处就是可以将数据进行分区,每一个小区内实现串行计算,分区采用Partitioner.Create实现

namespace ThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int j = 1; j <= 2; j++)
            {
                Console.WriteLine("\n第{0}次比较", j);
                ConcurrentBag<int> bag = new ConcurrentBag<int>();
                var watch = Stopwatch.StartNew();
                watch.Start();
                for (int i = 0; i < 3000000; i++)
                {
                    bag.Add(i);
                }
                Console.WriteLine("串行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds);
                GC.Collect();

                bag = new ConcurrentBag<int>();
                watch = Stopwatch.StartNew();
                watch.Start();
                Parallel.ForEach(Partitioner.Create(0, 3000000), i =>
                {
                    for (int m = i.Item1; m < i.Item2; m++)
                    {
                        bag.Add(m);
                    }
                });
                Console.WriteLine("并行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds);
                GC.Collect();

            }
        }
    }
}


这里还是要说一下:Partitioner.Create(0, 3000000)。

第一:我们要分区的范围是0-3000000。 

第二:我们肯定想知道系统给我们分了几个区? 很遗憾,这是系统内部协调的,无权告诉我们,当然系统也不反对我们自己指定分区个数,这里可以使用Partitioner.Create的第六个重载,比如这样:Partitioner.Create(0, 3000000, Environment.ProcessorCount), 因为 Environment.ProcessorCount能够获取到当前的硬件线程数,所以这里也就开了2个区。


下面分享下并行计算中我们可能有的疑惑?

<1> 如何中途退出并行循环? 

    是的,在串行代码中我们break一下就搞定了,但是并行就不是这么简单了,不过没关系,在并行循环的委托参数中提供了一个ParallelLoopState,该实例提供了Break和Stop方法来帮我们实现。

    Break: 当然这个是通知并行计算尽快的退出循环,比如并行计算正在迭代100,那么break后程序还会迭代所有小于100的。

    Stop:这个就不一样了,比如正在迭代100突然遇到stop,那它啥也不管了,直接退出。

namespace ThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            ConcurrentBag<int> bag = new ConcurrentBag<int>();

            Parallel.For(0, 20000000, (i, state) =>
            {
                if (bag.Count == 1000)
                {
                    state.Break();
                    //state.Stop();
                    return;
                }
                bag.Add(i);
            });

            Console.WriteLine("当前集合有{0}个元素。", bag.Count);

        }
    }
}

<2> 并行计算中抛出异常怎么处理?

 首先任务是并行计算的,处理过程中可能会产生n多的异常,那么如何来获取到这些异常呢?普通的Exception并不能获取到异常,然而为并行诞生的AggregateExcepation就可以获取到一组异常

namespace ThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Parallel.Invoke(Run1, Run2);
            }
            catch (AggregateException ex)
            {
                foreach (var single in ex.InnerExceptions)
                {
                    Console.WriteLine(single.Message);
                }
            }

            Console.Read();
        }

        static void Run1()
        {
            Thread.Sleep(3000);
            throw new Exception("我是任务1抛出的异常");
        }

        static void Run2()
        {
            Thread.Sleep(5000);
            throw new Exception("我是任务2抛出的异常");
        }
    }
}

<3> 并行计算中我可以留一个硬件线程出来吗?

  默认的情况下,底层机制会尽可能多的使用硬件线程,然而我们使用手动指定的好处是我们可以在2,4,8个硬件线程的情况下来进行测量加速比。

namespace ThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            ConcurrentBag<int> bag = new ConcurrentBag<int>();
            ParallelOptions options = new ParallelOptions();
            //指定使用的硬件线程数为1
            options.MaxDegreeOfParallelism = 1;
            var watch = Stopwatch.StartNew();
            watch.Start();
            Parallel.For(0, 300000, options, i =>
            {
                bag.Add(i);
            });

            Console.WriteLine("并行计算:集合有:{0}总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds);
            GC.Collect();

            bag = new ConcurrentBag<int>();
            //指定使用的硬件线程数为4
            options.MaxDegreeOfParallelism = 4;
            watch = Stopwatch.StartNew();
            watch.Start();
            Parallel.For(0, 300000, options, i =>
            {
                bag.Add(i);
            });

            Console.WriteLine("并行计算:集合有:{0}总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds);
            GC.Collect();
        }
    }
}

使用For和Foreach方法,Parallel类型自动为我们分配Task完成针对元素的一些工作。当然我们也可以直接使用Task,但是上面的这种形式,在语法上看上去更简洁了。

Parallel的Invoke方法,则为我们简化了启动一组并行操作,它隐式启动的就是Task。该方法接受Params Action[]参数,如下:

namespace ThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Parallel.Invoke(() =>
            {
                Console.WriteLine("任务1……");
            },
            () =>
            {
                Console.WriteLine("任务2……");
            },
            () =>
            {
                Console.WriteLine("任务3……");
            });
            Console.ReadKey();
        }
    }
}


Parallel中异常的的处理Demo

namespace ThreadDemo
{
    class Program
    {
        public static void Main()
        {
            try
            {
                Parallel.Invoke(() => { throw new Exception("Task1 Boom"); },
                                () => { throw new Exception("Task2 Boom"); });
            }
            catch (AggregateException ex)
            {
                ex.Handle(exception =>
                {
                    Console.WriteLine(exception.Message);
                    return true;
                });
            }
        }
    }
}

Parallel类小范例Demo1:

namespace ConsoleApplication
{
    class Program
    {
        static string EmulateProcessing(string taskName)
        {
            Thread.Sleep(TimeSpan.FromMilliseconds(
                new Random(DateTime.Now.Millisecond).Next(250, 350)));
            Console.WriteLine("{0} task was processed on a thread id {1}",
                    taskName, Thread.CurrentThread.ManagedThreadId);
            return taskName;
        }

        static void Main(string[] args)
        {
            Parallel.Invoke(
                () => EmulateProcessing("Task1"),
                () => EmulateProcessing("Task2"),
                () => EmulateProcessing("Task3")
            );

            var cts = new CancellationTokenSource();

            var result = Parallel.ForEach(
                Enumerable.Range(1, 30),
                new ParallelOptions
                {
                    CancellationToken = cts.Token,
                    MaxDegreeOfParallelism = Environment.ProcessorCount,
                    TaskScheduler = TaskScheduler.Default
                },
                (i, state) =>
                {
                    Console.WriteLine(string.Format("i={0},on a thread id{1}", i, Thread.CurrentThread.ManagedThreadId));
                    if (i == 20)
                    {
                        //state.Break();
                        //state.Stop();
                        //Console.WriteLine("Loop is stopped: {0}", state.IsStopped);
                    }
                });

            Console.WriteLine("---");
            Console.WriteLine("IsCompleted: {0}", result.IsCompleted);
            Console.WriteLine("Lowest break iteration: {0}", result.LowestBreakIteration);
        }
    }
}

Parallel类小范例Demo2:

namespace ThreadDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var watch = Stopwatch.StartNew();

            watch.Start();
            Run1();
            Run2();
            Console.WriteLine("我是串行开发,总共耗时:{0}\n", watch.ElapsedMilliseconds);
            watch.Restart();

            Parallel.Invoke(Run1, Run2);
            watch.Stop();
            Console.WriteLine("我是并行开发,总共耗时:{0}", watch.ElapsedMilliseconds);
        }

        static void Run1()
        {
            Console.WriteLine("我是任务一,我跑了3s");
            Thread.Sleep(3000);
        }

        static void Run2()
        {
            Console.WriteLine("我是任务二,我跑了5s");
            Thread.Sleep(5000);
        }
    }
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值