并行编程和任务(一)

前言

  并发、并行。同步、异步、互斥、多线程。我太难了。被这些词搞懵了。前面咱们在写.Net基础系列的时候写过了关于.Net的异步编程。那么其余的都是些什么东西呀。今天咱们首先就来解决这个问题。把这些词搞懂搞透。理清逻辑。而后最后咱们进入并行编程的介绍。git

概念初识

首先咱们看并发和并行:github

并发:并发指的是在操做系统中,一个是时间段内有多个程序在运行,可是呢。这几个程序都运行在同一个处理机上,而且任意时间点都是一个程序运行在处理机上。编程

并行:并行指的是在操做系统中,一个时间段内有多个程序在运行,可是呢。这几个程序分别运行在不一样的处理机上。也就是说这些程序是一块儿运行的。bash

简单理解也就是并发就像三个包子给一我的吃,一口吃一个包子。并行就是三个包子给三我的吃,三我的一口分别吃三个包子。多线程

而后咱们看看异步与多线程概念:并发

刚刚咱们讲到并发的理解概念,其中并发包含两种关系-同步和互斥。同步和互斥咱们都是相对于临界资源来谈的。异步

互斥:进程间相互排斥使用临界资源的现象就叫互斥。就比如进程A在访问List集合的时候,进程B也想访问,可是A在访问。B就阻塞等待A访问完成以后才去访问。异步编程

同步:进程间的关系不是临界资源的相互排斥,而是相互依赖。例如进程B须要读取一个集合结果,可是这个集合结果须要进程A返回,当进程A没有返回集合结果时,进程B就会由于没有得到信息而阻塞。当进程A返回信息。进程B就能够得到信息被唤起继续运行。oop

多线程:多线程能够说是程序设计的一个逻辑概念,多线程实现了线程的切换。使其看起来彷佛是在同时运行多个线程同样。是进程中并发运行的一段代码。性能

异步:异步与同步相对应。同步是进程间相互依赖。异步是进程间相互独立。不须要等待上一个进程的结果。能够作本身的事情。

上面咱们就介绍完了并发、并行、互斥、同步、多线程、异步。咱们总结下其中关联吧:

异步与多线程并不相等。异步是须要达到的目的,多线程是一个是实现异步的一种手段。最后达到的目的是什么呢?就是并发中线程的切换。同步也能够实现线程切换,可是因为同步中IO等待会浪费时间,因此同步切换进程与异步切换进行就有明显的时间差距。

Parallel

今天咱们介绍的是Parallel类。该类位于System.Threading.Tasks命名空间中。依次来实现数据和任务的并行性。

其中定义了并行的for和foreach的静态方法、还包含着Parallel.Invoke()用于任务的并行性。咱们下面就来看看吧。

Parallel.For()

Parallel.For()方法相似于#中的for循环语句,可是Parallel.For()是能够并行运行的。不过并行运行并不保证迭代运行的顺序。咱们来看看。

public static void ForEx()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            List<Test> list = new List<Test>();
            for (int i = 0; i < 100; i++)
            {
                Test test = new Test();
                test.Name = "Name:" + i;
                test.Index = "Index:" + i;
                test.Time = DateTime.Now;
                list.Add(test);
                Task.Delay(10).Wait();
                Console.WriteLine("C#中的for循环:" + i);
            }
            stopwatch.Stop();
            Console.WriteLine("for 0-100 执行完成 耗时:{0}", stopwatch.ElapsedMilliseconds);

        }
        public static void ParallelFor()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            List<Test> lists = new List<Test>();
            Parallel.For(1, 100, i =>
            {
                Test tests = new Test();
                tests.Name = "Name:" + i;
                tests.Index = "Index:" + i;
                tests.Time = DateTime.Now;
                lists.Add(tests);
                Task.Delay(10).Wait();
                Console.WriteLine("Parallel中的ParallelFor循环:" + i);
            });
            stopwatch.Stop();
            Console.WriteLine("ParallelFor 0-100 执行完成 耗时:{0}", stopwatch.ElapsedMilliseconds);
        }复制代码
static void Main(string[] args)
        {
            Console.WriteLine("Start");
            ForEx();
            Console.WriteLine("for循环完成");
            ParallelFor();
            Console.WriteLine("End");
        }复制代码

这里咱们能够看到最后的运行结果图使用for循环的执行下来都是依次执行。按照相应的顺序。可是咱们使用Parallel.For()的时候运行下来。也输出了全部的结果,可是其顺序就没有保证了。

Parallel.ForEach()

咱们再看Parallel.ForEach()提供了一个并行处理数据的机制。这里相似于foreach语句,可是是以一部方式遍历。这里没有肯定遍历的顺序,其执行顺序也就是不保证的。

#region ForEach 语句比较 
        public static void ParallelForEach()
        {
            List<Test> result = new List<Test>();
            for (int i = 1; i < 100; i++)
            {
                Test model = new Test();
                model.Name = "Name" + i;
                model.Index = "Index" + i;
                model.Time = DateTime.Now;
                result.Add(model);
            }
            Parallel.ForEach<Test>(result, s => {
                Console.WriteLine(s.Name);
            });
        }
        #endregion 复制代码
static void Main(string[] args)
        {
            ParallelForEach();
        }复制代码

咱们看这里的运行结果,对数据集合进行了遍历处理,可是其运行的顺序不定,是乱序的结果。这也就是异步遍历的一个表现。

ParallelLoopState

下面咱们来看ParallelLoopState。它提供了两个方法。一个是Break、一个是Stop。

Break:表示并行循环执行了当前迭代后应尽快中止执行。筛选出符合条件的执行,可能输出彻底。

Stop:表示并行循环应尽快中止执行。遇到符合条件后中止并行循环,可能不彻底输出。

下面咱们看看代码:

public static List<Test> GetListTest()
        {
            List<Test> result = new List<Test>();
            for (int i = 1; i < 100; i++)
            {
                Test model = new Test();
                model.Name = i.ToString();
                model.Index = "Index" + i;
                model.Time = DateTime.Now;
                result.Add(model);
            }
            return result;
        }
        public static void BraekFor()
        {
            var result = GetListTest();
            Parallel.For(0, result.Count, (int i, ParallelLoopState ls) =>
            {
                Task.Delay(10).Wait();
                if (i > 50)
                {
                    Console.WriteLine("Parallel.For使用Break中止当前迭代:" + i);
                    ls.Break();
                    return;
                }
                Console.WriteLine("测试Parallel.For的Break:" + i);
            });

        }

        public static void StopFor()
        {
            var result = GetListTest();
            Parallel.For(0, result.Count, (int i, ParallelLoopState ls) =>
            {
                Task.Delay(10).Wait();
                if (i > 50)
                {
                    Console.WriteLine("Parallel.For使用Stop中止 迭代:" + i);
                    ls.Stop();
                    return;
                }
                Console.WriteLine("测试Parallel.For的Stop:" + i);
            });
        }复制代码
static void Main(string[] args)
        {
            BraekFor();
            StopFor();
        }复制代码

咱们看对于Parallel.For()来讲这个案例。使用Break()中止当前迭代会输出符合条件全部结果,可是咱们使用Stop的时候输出部分的时候就中止了。

public static void BraekForEach()
        {
            var result = GetListTest();
            Parallel.ForEach<Test>(result, (Test s, ParallelLoopState ls) =>
            {
                Task.Delay(10).Wait();
                if (Convert.ToInt32(s.Name) > 50)
                {
                    Console.WriteLine("Parallel.ForEach使用Break中止当前迭代:" + s.Name);
                    ls.Break();
                    return;
                }
                Console.WriteLine("测试Parallel.ForEach的Break:" + s.Name);
            });

        }

        public static void StopForEach()
        {
            var result = GetListTest();
            Parallel.ForEach<Test>(result, (Test s, ParallelLoopState ls) =>
            {
                Task.Delay(10).Wait();
                if (Convert.ToInt32(s.Name) > 50)
                {
                    Console.WriteLine("Parallel.ForEach使用Stop中止 迭代:" + s.Name);
                    ls.Stop();
                    return;
                }
                Console.WriteLine("测试Parallel.ForEach的Stop:" + s.Name);
            });
        }复制代码
static void Main(string[] args)
        {
            BraekForEach();
            StopForEach();
        }复制代码

咱们再对Parallel.ForEach进行测试,发现对于Stop和Break的用法和意义是同样的。

Parallel.Invoke()

上面咱们介绍了Parallel.For和Parallel.ForEach以及提供的两个方法Break和Stop。上面介绍的这些都是对数据的并行处理执行。下面咱们介绍Parallel.Invoke()。它是针对于任务的并行运行处理。

这里咱们须要注意如下几点:

一、若是咱们传入4个任务并行,那么咱们至少须要四个逻辑处理内核(硬件线程)才可能使四个任务一块儿运行。可是当其中一个内核繁忙,那么底层的调度逻辑就可能会延迟某些方法的初始化执行。

二、Parallel.Invoke()所包含的并行任务不能相互依赖,由于运行执行的顺序不可保证。

三、使用Parallel.Invoke()咱们须要测试运行结果,观察逻辑内核使用率以及实现加速。

四、使用Parallel.Invoke()会产生一些额外的开销,例如分配硬件线程。

咱们看下面的案例:

下面咱们对一个集合的数据进行添加而后输出。下面咱们分为四组测试。500条数据和1000条数据各两个,分别是通常的同步任务和Parallel.Invoke()的并行任务执行。再观察其运行的时间比较。

#region Parallel.Invoke()使用共同资源

        public static List<Test> _tests = null;
        public static void TaskFive_One()
        {
            for (int i = 0; i < 500; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskFive_One 500条数据第一个方法 执行完成");
        }
        public static void TaskFive_Two()
        {
            for (int i = 500; i < 1000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskFive_Two 500条数据第二个方法 执行完成");
        }
        public static void TaskFive_Three()
        {
            for (int i = 1000; i < 1500; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskFive_Three 500条数据第三个方法 执行完成");
        }
        public static void TaskFive_Four()
        {
            for (int i = 1500; i < 2000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskFive_Four 500条数据第四个方法 执行完成");
        }



        public static void TaskOnethousand_One()
        {
            for (int i = 0; i < 1000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_One 1000条数据第一个方法 执行完成");
        }
        public static void TaskOnethousand_Two()
        {
            for (int i = 1000; i < 2000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Two 1000条数据第二个方法 执行完成");
        }
        public static void TaskOnethousand_Three()
        {
            for (int i = 2000; i < 3000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Three 1000条数据第三个方法 执行完成");
        }
        public static void TaskOnethousand_Four()
        {
            for (int i = 3000; i < 4000; i++)
            {
                Test test = new Test();
                test.Name = i.ToString();
                test.Index = i.ToString();
                test.Time = DateTime.Now;
                _tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Three 1000条数据第四个方法 执行完成");
        }
        #endregion复制代码
static void Main(string[] args)
        {
            //五百条数据顺序完成
            Stopwatch swFive = new Stopwatch();
            swFive.Start();
            Thread.Sleep(3000);
            _tests = new List<Test>();
            TaskFive_One();
            TaskFive_Two();
            TaskFive_Three();
            TaskFive_Four();
            swFive.Stop();
            Console.WriteLine("500条数据 顺序编程所耗时间:" + swFive.ElapsedMilliseconds);


            //五百条数据并行完成
            Stopwatch swFiveTask = new Stopwatch();
            swFiveTask.Start();
            Thread.Sleep(3000);
            _tests = new List<Test>();
            Parallel.Invoke(() => TaskFive_One(), () => TaskFive_Two(), () => TaskFive_Three(), () => TaskFive_Four());
            swFiveTask.Stop();
            Console.WriteLine("500条数据 并行编程所耗时间:" + swFiveTask.ElapsedMilliseconds);


            //一千条数据顺序完成
            Stopwatch swOnethousand = new Stopwatch();
            swOnethousand.Start();
            Thread.Sleep(3000);
            _tests = new List<Test>();
            TaskOnethousand_One();
            TaskOnethousand_Two();
            TaskOnethousand_Three();
            TaskOnethousand_Four();
            swOnethousand.Stop();
            Console.WriteLine("1000条数据 顺序编程所耗时间:" + swOnethousand.ElapsedMilliseconds);


            //一千条数据并行完成
            Stopwatch swOnethousandTask = new Stopwatch();
            swOnethousandTask.Start();
            Thread.Sleep(3000);
            _tests = new List<Test>();
            Parallel.Invoke(() => TaskOnethousand_One(), () => TaskOnethousand_Two(), () => TaskOnethousand_Three(), () => TaskOnethousand_Four());
            swOnethousandTask.Stop();
            Console.WriteLine("1000条数据 并行编程所耗时间:" + swOnethousandTask.ElapsedMilliseconds);
        }复制代码

咱们看此次的运行结果,发现咱们使用顺序编程和并行编程所须要的时间相差无几的。那么怎么回事呢?咱们仔细检查下,发现咱们彷佛对资源进行了共享。咱们下面处理下,对list集合不进行共享看看。

#region Parallel.Invoke()不使用共同资源

        public static void TaskFive_One()
        {
            List<Test> tests = new List<Test>();
            for (int i = 0; i < 500; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskFive_One 500条数据第一个方法 执行完成");
        }
        public static void TaskFive_Two()
        {
            List<Test> tests = new List<Test>();
            for (int i = 500; i < 1000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskFive_Two 500条数据第二个方法 执行完成");
        }
        public static void TaskFive_Three()
        {
            List<Test> tests = new List<Test>();
            for (int i = 1000; i < 1500; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskFive_Three 500条数据第三个方法 执行完成");
        }
        public static void TaskFive_Four()
        {
            List<Test> tests = new List<Test>();
            for (int i = 1500; i < 2000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskFive_Four 500条数据第四个方法 执行完成");
        }



        public static void TaskOnethousand_One()
        {
            List<Test> tests = new List<Test>();
            for (int i = 0; i < 1000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_One 1000条数据第一个方法 执行完成");
        }
        public static void TaskOnethousand_Two()
        {
            List<Test> tests = new List<Test>();
            for (int i = 1000; i < 2000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Two 1000条数据第二个方法 执行完成");
        }
        public static void TaskOnethousand_Three()
        {
            List<Test> tests = new List<Test>();
            for (int i = 2000; i < 3000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Three 1000条数据第三个方法 执行完成");
        }
        public static void TaskOnethousand_Four()
        {
            List<Test> tests = new List<Test>();
            for (int i = 3000; i < 4000; i++)
            {
                Test test = new Test();
                test.Name = "Name" + i.ToString();
                test.Index = "Index" + i.ToString();
                test.Time = DateTime.Now;
                tests.Add(test);
            }
            Console.WriteLine("TaskOnethousand_Four 1000条数据第四个方法 执行完成");
        }
        #endregion复制代码

static void Main(string[] args)
        {
            Stopwatch swTest = new Stopwatch();
            swTest.Start();
            Thread.Sleep(3000);
            TaskFive_One();
            TaskFive_Two();
            TaskFive_Three();
            TaskFive_Four();
            swTest.Stop();
            Console.WriteLine("500条数据 顺序编程所耗时间:" + swTest.ElapsedMilliseconds);


            //五百条数据并行完成 
            swTest.Restart();
            Thread.Sleep(3000);
            Parallel.Invoke(() => TaskFive_One(), () => TaskFive_Two(), () => TaskFive_Three(), () => TaskFive_Four());
            swTest.Stop();
            Console.WriteLine("500条数据 并行编程所耗时间:" + swTest.ElapsedMilliseconds);


            //一千条数据顺序完成 
            swTest.Restart();
            Thread.Sleep(3000);
            TaskOnethousand_One();
            TaskOnethousand_Two();
            TaskOnethousand_Three();
            TaskOnethousand_Four();
            swTest.Stop();
            Console.WriteLine("1000条数据 顺序编程所耗时间:" + swTest.ElapsedMilliseconds);


            //一千条数据并行完成 
            swTest.Restart();
            Thread.Sleep(3000);
            Parallel.Invoke(() => TaskOnethousand_One(), () => TaskOnethousand_Two(), () => TaskOnethousand_Three(), () => TaskOnethousand_Four());
            swTest.Stop();
            Console.WriteLine("1000条数据 并行编程所耗时间:" + swTest.ElapsedMilliseconds);
        }复制代码

  咱们看下咱们修改共享资源后,对于500条数据的运行结果,顺序编程比并行编程仍是要快点,可是在1000条数据的时候并行编程就明显比顺序编程要快了。并且在测试中并行编程的运行顺序也是不固定的。咱们在平常编程中咱们须要衡量咱们的应用是否须要并行编程,否则可能形成更多的性能损耗。

项目源码地址

世界上那些最容易的事情中,拖延时间最不费力。坚韧是成功的一大要素,只要在门上敲得够久够大声,终会把人唤醒的。

 欢迎你们扫描下方二维码,和我一块儿学习更多的知识😊

 

相关文章
相关标签/搜索