C# 多線程,論多核時代愛恨情仇
博客前言
為什么要學習多線程?
2010年1月21日是10年某市公務(wù)員考試的報名截止日。因從下午2點開始,用于報名的北京市人事考試網(wǎng)癱瘓,原定于昨天下午5點截止的報名時間延遲至今天上午11點。
2011年3月11日下午5時(北京時間12日早9點),蘋果發(fā)布新一代的平板電腦產(chǎn)品iPad 2,配備了A5.1Ghz雙核處理器,這寓意著平板電腦和筆記本一同進入"多核時代"。
同年6月18日,國內(nèi)著名B2C---京東在周年慶典之際舉行了"隆重"的大規(guī)模的促銷活動,搶購隨之而來,訂單擠爆京東 限時達臨時取消。 昨天有消費者反映,由于點擊量過大,18日早上京東商城網(wǎng)站一度癱瘓。一位消費者說:“18日凌晨1點開始就登不上京東商城。”劉強東也表示:由于流量多 次超過4個G,服務(wù)器運行緩慢。 昨天,京東商城官網(wǎng)發(fā)布公告稱,“‘618’活動異?;鸨矣脩粝聠嗡俣瓤涨?,致使部分用戶已購訂單顯示出現(xiàn)延遲,用戶在一段時間內(nèi)無法在‘我的京東’中 查詢到自己的訂單。目前已購訂單顯示延遲的問題已得到有效解決,對此給您帶來的不便,我們深表歉意。”
2015年05月05日登錄風信子網(wǎng)上商城發(fā)現(xiàn),首頁除了廣告和相關(guān)消息外,只有“注冊賬號獲取更多優(yōu)惠”這唯一一個按鈕,沒有商品展示,沒有產(chǎn) 品搜索,不能網(wǎng)上下單,甚至連進入商城的按鈕也沒有。風信子南沙跨境商品直購體驗中心相關(guān)負責人表示,這主要是因為預約的人數(shù)太多,截至五一,預約人數(shù)已 超過十萬,太多人頻繁登陸,導致網(wǎng)店服務(wù)器癱瘓,目前技術(shù)人員還在努力維修中。該負責人介紹,體驗中心的網(wǎng)站目前正在調(diào)試,“網(wǎng)站目前的作用主要是給市民 預約和提前注冊,通過網(wǎng)絡(luò)注冊的市民不用在現(xiàn)場驗證身份證等信息,可以提高購買效率。”
下面通過一些實例來認識一下多線程和讓大家知道為什么要學習多線程。
寫在前面
老板只有兩種,好的和壞的。好的老板只跟你談錢,壞的老板只跟你談理想。
v模擬場景
假設(shè)后臺有個monitor時事的在監(jiān)測訂單,且可以發(fā)現(xiàn)訂單然后處理訂單,每次(1次/S)可以處理1千個訂單,需要向倉庫系統(tǒng)發(fā)出指令,讓他們負責配送發(fā)貨。那么我們來寫一個EmulationSystem(模擬系統(tǒng))
JobHelper因為我們只是為了模擬一個環(huán)境,所以它是這樣的。
- //------------------------------------------------------------------------------
- // <copyright file="JobHelper.cs" company="CNBlogs Corporation" owner="請叫我頭頭哥">
- // Copyright (C) 2015-2016 All Rights Reserved
- // 原博文地址: http://www.cnblogs.com/toutou/
- // </copyright>
- //------------------------------------------------------------------------------
- namespace CNBlogs.Common.Shared
- {
- using System.Threading;
- /// <summary>
- /// Job helper
- /// </summary>
- public class JobHelper
- {
- /// <summary>
- /// Get job total count
- /// </summary>
- /// <returns></returns>
- public int GetJobCount()
- {
- // 我們的側(cè)重點是多線程,所以我們就模擬每次有1千個訂單,而模擬,所以我們不關(guān)心其他的。只要訂單數(shù)量。
- return 1000;
- }
- /// <summary>
- /// Submit job
- /// </summary>
- /// <param name="jobId">For job id</param>
- /// <returns>Submit job status</returns>
- public bool SubmitJob(int jobId)
- {
- // 假設(shè)針對每個訂單向后臺發(fā)送任務(wù)需要1秒,而且每個訂單都能成功發(fā)送
- Thread.Sleep(1000);
- return true;
- }
- }
- }
背景我們也交待完了,現(xiàn)在需要來得到這些訂單信息以后,向倉庫submit這些訂單。
v實戰(zhàn)演習
根據(jù)這種背景,我們可以有很多處理的辦法。
-
傳統(tǒng)辦法:
(開啟無腦模式:未使用多線程)
代碼正文:
- //------------------------------------------------------------------------------
- // <copyright file="Runner.cs" company="CNBlogs Corporation" owner="請叫我頭頭哥">
- // Copyright (C) 2015-2016 All Rights Reserved
- // 原博文地址: http://www.cnblogs.com/toutou/
- // </copyright>
- //------------------------------------------------------------------------------
- namespace CNBlogs.EmulationSystem
- {
- using System;
- using System.Threading;
- using CNBlogs.Common.Shared;
- class Runner
- {
- /// <summary>
- /// Job helper
- /// </summary>
- private static JobHelper jobHelper = new JobHelper();
- /// <summary>
- /// Runner lock
- /// </summary>
- private static Mutex mutex;
- static void Main(string[] args)
- {
- // 記錄開始處理的時間
- DateTime paddingTime = DateTime.Now.ToLocalTime();
- int orderCount = jobHelper.GetJobCount();
- // 用一個指示調(diào)用線程是否應(yīng)擁有互斥體的初始所屬權(quán)的布爾值來初始化 Mutex 類的新實例。
- // 當前進程只能啟動一次
- mutex = new Mutex(false, "CNBlogs.EmulationSystem");
- if (!mutex.WaitOne(0, false))
- {
- Console.Out.WriteLine("Monitor already running...");
- return;
- }
- for (int i = 0; i < orderCount; i++)
- {
- // 假設(shè)i就是job id
- jobHelper.SubmitJob(i);
- }
- // 記錄處理完成的時間
- DateTime completeTime = DateTime.Now.ToLocalTime();
- var minutes = (completeTime - paddingTime).TotalSeconds;
- Console.WriteLine(string.Format("處理{0}個訂單,花費時間{1}秒", orderCount, minutes));
- }
- }
- }
PS:現(xiàn)在的這些個電商,動不動來個什么周年慶店慶什么雙11雙12的一頓突突,搞得咱這些老百姓就全部蜂擁而上,顯然如果用傳統(tǒng)的方法雖然不會出錯,但是老板肯定會找你喝茶。在多核時代用這種方法也只能恨鐵不成鋼了。所以這個方法是絕對不可取的。
-
Task方法:
如果有對Task不是很熟悉的園友可以在這里解開心謎。
代碼正文:
- //------------------------------------------------------------------------------
- // <copyright file="Runner.cs" company="CNBlogs Corporation" owner="請叫我頭頭哥">
- // Copyright (C) 2015-2016 All Rights Reserved
- // 原博文地址: http://www.cnblogs.com/toutou/
- // </copyright>
- //------------------------------------------------------------------------------
- namespace CNBlogs.EmulationSystem
- {
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using System.Threading.Tasks;
- using CNBlogs.Common.Shared;
- class Runner
- {
- /// <summary>
- /// Job helper
- /// </summary>
- private static JobHelper jobHelper = new JobHelper();
- /// <summary>
- /// Runner lock
- /// </summary>
- private static Mutex mutex;
- static void Main(string[] args)
- {
- // 記錄開始處理的時間
- DateTime paddingTime = DateTime.Now.ToLocalTime();
- int orderCount = jobHelper.GetJobCount();
- // 用一個指示調(diào)用線程是否應(yīng)擁有互斥體的初始所屬權(quán)的布爾值來初始化 Mutex 類的新實例。
- // 當前進程只能啟動一次
- mutex = new Mutex(false, "CNBlogs.EmulationSystem");
- if (!mutex.WaitOne(0, false))
- {
- Console.Out.WriteLine("Monitor already running...");
- return;
- }
- var taskList = new List<Task>();
- for (int i = 0; i < orderCount; i++)
- {
- int temp=i;
- taskList.Add(Task.Factory.StartNew(() =>
- {
- // 假設(shè)i就是job id
- jobHelper.SubmitJob(temp);
- }));
- }
- // 等待所有task執(zhí)行完畢
- Task.WaitAll(taskList.ToArray());
- // 記錄處理完成的時間
- DateTime completeTime = DateTime.Now.ToLocalTime();
- var minutes = (completeTime - paddingTime).TotalSeconds;
- Console.WriteLine(string.Format("Complete: {0},cost {1} s", orderCount, minutes));
- }
- }
- }
代碼效果:
相信分別從有TASK和沒有TASK的這兩次demo中,可以清楚的發(fā)現(xiàn)多線程處理這些頻繁交互能力的魅力。你會愛上這個方法。確實提高的效率。但是問題也隨之而來,一個進程多線程的總數(shù)和大小是有要求的(這個取決于服務(wù)器的配置),不是任由我們肆意的開采的。如果訂單太多,我們不控制task那是會拉仇恨的。task需要控制。
-
Semaphore:
如果有對Semaphore不是很熟悉的園友可以在這里解開心謎。
代碼正文:
- //------------------------------------------------------------------------------
- // <copyright file="Runner.cs" company="CNBlogs Corporation" owner="請叫我頭頭哥">
- // Copyright (C) 2015-2016 All Rights Reserved
- // 原博文地址: http://www.cnblogs.com/toutou/
- // </copyright>
- //------------------------------------------------------------------------------
- namespace CNBlogs.EmulationSystem
- {
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using System.Threading.Tasks;
- using CNBlogs.Common.Shared;
- class Runner
- {
- /// <summary>
- /// Job helper
- /// </summary>
- private static JobHelper jobHelper = new JobHelper();
- /// <summary>
- /// The locker used to lock the semaphore and thread.
- /// </summary>
- private static object lockerObj = new object();
- /// <summary>
- /// The semaphore limit the thread number of get latest test info
- /// </summary>
- private static Semaphore semaphoreLimit;
- /// <summary>
- /// Runner lock
- /// </summary>
- private static Mutex mutex;
- static void Main(string[] args)
- {
- // 記錄開始處理的時間
- DateTime paddingTime = DateTime.Now.ToLocalTime();
- int orderCount = jobHelper.GetJobCount();
- // 用一個指示調(diào)用線程是否應(yīng)擁有互斥體的初始所屬權(quán)的布爾值來初始化 Mutex 類的新實例。
- // 當前進程只能啟動一次
- mutex = new Mutex(false, "CNBlogs.EmulationSystem");
- if (!mutex.WaitOne(0, false))
- {
- Console.Out.WriteLine("Monitor already running...");
- return;
- }
- // 假設(shè)我們的服務(wù)器一個進程只能接受的大小=當前線程大小*500 ps:500是設(shè)置信號量的最大值
- int maxProcNumber = 500;
- using (semaphoreLimit = new Semaphore(0, maxProcNumber))
- {
- // 以指定的次數(shù)退出信號量并返回前一個計數(shù)。
- semaphoreLimit.Release(maxProcNumber);
- var taskList = new List<Task>();
- for (int i = 0; i < orderCount; i++)
- {
- int temp=i;
- // 如果SubmitJob有IO或者其他容易因為沖突而引起異常的話,這里需要加上lock
- //lock (lockerObj)
- //{
- semaphoreLimit.WaitOne();
- taskList.Add(Task.Factory.StartNew(() =>
- {
- // 假設(shè)i就是job id
- jobHelper.SubmitJob(temp);
- // 退出信號量并返回前一個計數(shù)。
- semaphoreLimit.Release();
- }));
- //}
- }
- // 等待所有task執(zhí)行完畢
- Task.WaitAll(taskList.ToArray());
- }
- // 記錄處理完成的時間
- DateTime completeTime = DateTime.Now.ToLocalTime();
- var minutes = (completeTime - paddingTime).TotalSeconds;
- Console.WriteLine(string.Format("Complete: {0},cost {1} s", orderCount, minutes));
- }
- }
- }
代碼效果:
v博客總結(jié)
多線程路還很長...