並行編程和任務(一)

前言

  併發、並行。同步、異步、互斥、多線程。我太難了。被這些詞搞懵了。前面咱們在寫.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條數據的時候並行編程就明顯比順序編程要快了。並且在測試中並行編程的運行順序也是不固定的。咱們在平常編程中咱們須要衡量咱們的應用是否須要並行編程,否則可能形成更多的性能損耗。

項目源碼地址

世界上那些最容易的事情中,拖延時間最不費力。堅韌是成功的一大要素,只要在門上敲得夠久夠大聲,終會把人喚醒的。

 歡迎你們掃描下方二維碼,和我一塊兒學習更多的知識😊

 

相關文章
相關標籤/搜索