C# 多線程,論多核時(shí)代愛(ài)恨情仇
博客前言
為什么要學(xué)習(xí)多線程?
2010年1月21日是10年某市公務(wù)員考試的報(bào)名截止日。因從下午2點(diǎn)開(kāi)始,用于報(bào)名的北京市人事考試網(wǎng)癱瘓,原定于昨天下午5點(diǎn)截止的報(bào)名時(shí)間延遲至今天上午11點(diǎn)。
2011年3月11日下午5時(shí)(北京時(shí)間12日早9點(diǎn)),蘋(píng)果發(fā)布新一代的平板電腦產(chǎn)品iPad 2,配備了A5.1Ghz雙核處理器,這寓意著平板電腦和筆記本一同進(jìn)入"多核時(shí)代"。
同年6月18日,國(guó)內(nèi)著名B2C---京東在周年慶典之際舉行了"隆重"的大規(guī)模的促銷(xiāo)活動(dòng),搶購(gòu)隨之而來(lái),訂單擠爆京東 限時(shí)達(dá)臨時(shí)取消。 昨天有消費(fèi)者反映,由于點(diǎn)擊量過(guò)大,18日早上京東商城網(wǎng)站一度癱瘓。一位消費(fèi)者說(shuō):“18日凌晨1點(diǎn)開(kāi)始就登不上京東商城。”劉強(qiáng)東也表示:由于流量多 次超過(guò)4個(gè)G,服務(wù)器運(yùn)行緩慢。 昨天,京東商城官網(wǎng)發(fā)布公告稱,“‘618’活動(dòng)異常火爆且用戶下單速度空前,致使部分用戶已購(gòu)訂單顯示出現(xiàn)延遲,用戶在一段時(shí)間內(nèi)無(wú)法在‘我的京東’中 查詢到自己的訂單。目前已購(gòu)訂單顯示延遲的問(wèn)題已得到有效解決,對(duì)此給您帶來(lái)的不便,我們深表歉意。”
2015年05月05日登錄風(fēng)信子網(wǎng)上商城發(fā)現(xiàn),首頁(yè)除了廣告和相關(guān)消息外,只有“注冊(cè)賬號(hào)獲取更多優(yōu)惠”這唯一一個(gè)按鈕,沒(méi)有商品展示,沒(méi)有產(chǎn) 品搜索,不能網(wǎng)上下單,甚至連進(jìn)入商城的按鈕也沒(méi)有。風(fēng)信子南沙跨境商品直購(gòu)體驗(yàn)中心相關(guān)負(fù)責(zé)人表示,這主要是因?yàn)轭A(yù)約的人數(shù)太多,截至五一,預(yù)約人數(shù)已 超過(guò)十萬(wàn),太多人頻繁登陸,導(dǎo)致網(wǎng)店服務(wù)器癱瘓,目前技術(shù)人員還在努力維修中。該負(fù)責(zé)人介紹,體驗(yàn)中心的網(wǎng)站目前正在調(diào)試,“網(wǎng)站目前的作用主要是給市民 預(yù)約和提前注冊(cè),通過(guò)網(wǎng)絡(luò)注冊(cè)的市民不用在現(xiàn)場(chǎng)驗(yàn)證身份證等信息,可以提高購(gòu)買(mǎi)效率。”
下面通過(guò)一些實(shí)例來(lái)認(rèn)識(shí)一下多線程和讓大家知道為什么要學(xué)習(xí)多線程。
寫(xiě)在前面
老板只有兩種,好的和壞的。好的老板只跟你談錢(qián),壞的老板只跟你談理想。
v模擬場(chǎng)景
假設(shè)后臺(tái)有個(gè)monitor時(shí)事的在監(jiān)測(cè)訂單,且可以發(fā)現(xiàn)訂單然后處理訂單,每次(1次/S)可以處理1千個(gè)訂單,需要向倉(cāng)庫(kù)系統(tǒng)發(fā)出指令,讓他們負(fù)責(zé)配送發(fā)貨。那么我們來(lái)寫(xiě)一個(gè)EmulationSystem(模擬系統(tǒng))

JobHelper因?yàn)槲覀冎皇菫榱四M一個(gè)環(huán)境,所以它是這樣的。
- //------------------------------------------------------------------------------
 - // <copyright file="JobHelper.cs" company="CNBlogs Corporation" owner="請(qǐng)叫我頭頭哥">
 - // 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è)重點(diǎn)是多線程,所以我們就模擬每次有1千個(gè)訂單,而模擬,所以我們不關(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è)針對(duì)每個(gè)訂單向后臺(tái)發(fā)送任務(wù)需要1秒,而且每個(gè)訂單都能成功發(fā)送
 - Thread.Sleep(1000);
 - return true;
 - }
 - }
 - }
 
背景我們也交待完了,現(xiàn)在需要來(lái)得到這些訂單信息以后,向倉(cāng)庫(kù)submit這些訂單。
v實(shí)戰(zhàn)演習(xí)
根據(jù)這種背景,我們可以有很多處理的辦法。
- 
    
傳統(tǒng)辦法:
(開(kāi)啟無(wú)腦模式:未使用多線程)
代碼正文:
- //------------------------------------------------------------------------------
 - // <copyright file="Runner.cs" company="CNBlogs Corporation" owner="請(qǐng)叫我頭頭哥">
 - // 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)
 - {
 - // 記錄開(kāi)始處理的時(shí)間
 - DateTime paddingTime = DateTime.Now.ToLocalTime();
 - int orderCount = jobHelper.GetJobCount();
 - // 用一個(gè)指示調(diào)用線程是否應(yīng)擁有互斥體的初始所屬權(quán)的布爾值來(lái)初始化 Mutex 類(lèi)的新實(shí)例。
 - // 當(dāng)前進(jìn)程只能啟動(dòng)一次
 - 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);
 - }
 - // 記錄處理完成的時(shí)間
 - DateTime completeTime = DateTime.Now.ToLocalTime();
 - var minutes = (completeTime - paddingTime).TotalSeconds;
 - Console.WriteLine(string.Format("處理{0}個(gè)訂單,花費(fèi)時(shí)間{1}秒", orderCount, minutes));
 - }
 - }
 - }
 

PS:現(xiàn)在的這些個(gè)電商,動(dòng)不動(dòng)來(lái)個(gè)什么周年慶店慶什么雙11雙12的一頓突突,搞得咱這些老百姓就全部蜂擁而上,顯然如果用傳統(tǒng)的方法雖然不會(huì)出錯(cuò),但是老板肯定會(huì)找你喝茶。在多核時(shí)代用這種方法也只能恨鐵不成鋼了。所以這個(gè)方法是絕對(duì)不可取的。
 - 
    
Task方法:
如果有對(duì)Task不是很熟悉的園友可以在這里解開(kāi)心謎。
代碼正文:
- //------------------------------------------------------------------------------
 - // <copyright file="Runner.cs" company="CNBlogs Corporation" owner="請(qǐng)叫我頭頭哥">
 - // 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)
 - {
 - // 記錄開(kāi)始處理的時(shí)間
 - DateTime paddingTime = DateTime.Now.ToLocalTime();
 - int orderCount = jobHelper.GetJobCount();
 - // 用一個(gè)指示調(diào)用線程是否應(yīng)擁有互斥體的初始所屬權(quán)的布爾值來(lái)初始化 Mutex 類(lèi)的新實(shí)例。
 - // 當(dāng)前進(jìn)程只能啟動(dòng)一次
 - 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());
 - // 記錄處理完成的時(shí)間
 - DateTime completeTime = DateTime.Now.ToLocalTime();
 - var minutes = (completeTime - paddingTime).TotalSeconds;
 - Console.WriteLine(string.Format("Complete: {0},cost {1} s", orderCount, minutes));
 - }
 - }
 - }
 
代碼效果:

相信分別從有TASK和沒(méi)有TASK的這兩次demo中,可以清楚的發(fā)現(xiàn)多線程處理這些頻繁交互能力的魅力。你會(huì)愛(ài)上這個(gè)方法。確實(shí)提高的效率。但是問(wèn)題也隨之而來(lái),一個(gè)進(jìn)程多線程的總數(shù)和大小是有要求的(這個(gè)取決于服務(wù)器的配置),不是任由我們肆意的開(kāi)采的。如果訂單太多,我們不控制task那是會(huì)拉仇恨的。task需要控制。

 - 
    
Semaphore:
如果有對(duì)Semaphore不是很熟悉的園友可以在這里解開(kāi)心謎。
代碼正文:
- //------------------------------------------------------------------------------
 - // <copyright file="Runner.cs" company="CNBlogs Corporation" owner="請(qǐng)叫我頭頭哥">
 - // 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)
 - {
 - // 記錄開(kāi)始處理的時(shí)間
 - DateTime paddingTime = DateTime.Now.ToLocalTime();
 - int orderCount = jobHelper.GetJobCount();
 - // 用一個(gè)指示調(diào)用線程是否應(yīng)擁有互斥體的初始所屬權(quán)的布爾值來(lái)初始化 Mutex 類(lèi)的新實(shí)例。
 - // 當(dāng)前進(jìn)程只能啟動(dòng)一次
 - mutex = new Mutex(false, "CNBlogs.EmulationSystem");
 - if (!mutex.WaitOne(0, false))
 - {
 - Console.Out.WriteLine("Monitor already running...");
 - return;
 - }
 - // 假設(shè)我們的服務(wù)器一個(gè)進(jìn)程只能接受的大小=當(dāng)前線程大小*500 ps:500是設(shè)置信號(hào)量的最大值
 - int maxProcNumber = 500;
 - using (semaphoreLimit = new Semaphore(0, maxProcNumber))
 - {
 - // 以指定的次數(shù)退出信號(hào)量并返回前一個(gè)計(jì)數(shù)。
 - semaphoreLimit.Release(maxProcNumber);
 - var taskList = new List<Task>();
 - for (int i = 0; i < orderCount; i++)
 - {
 - int temp=i;
 - // 如果SubmitJob有IO或者其他容易因?yàn)闆_突而引起異常的話,這里需要加上lock
 - //lock (lockerObj)
 - //{
 - semaphoreLimit.WaitOne();
 - taskList.Add(Task.Factory.StartNew(() =>
 - {
 - // 假設(shè)i就是job id
 - jobHelper.SubmitJob(temp);
 - // 退出信號(hào)量并返回前一個(gè)計(jì)數(shù)。
 - semaphoreLimit.Release();
 - }));
 - //}
 - }
 - // 等待所有task執(zhí)行完畢
 - Task.WaitAll(taskList.ToArray());
 - }
 - // 記錄處理完成的時(shí)間
 - DateTime completeTime = DateTime.Now.ToLocalTime();
 - var minutes = (completeTime - paddingTime).TotalSeconds;
 - Console.WriteLine(string.Format("Complete: {0},cost {1} s", orderCount, minutes));
 - }
 - }
 - }
 
代碼效果:

 
v博客總結(jié)
多線程路還很長(zhǎng)...















 
 
 






 
 
 
 