为什么要学习多线程?
2010年1月21日是10年某市公务员考试的报名截止日。因从下午2点开始,用于报名的北京市人事考试网瘫痪,原定于昨天下午5点截止的报名时间延迟至今天上午11点。
2011年3月11日下午5时(北京时间12日早9点),苹果发布新一代的平板电脑产品iPad 2,配备了A5.1Ghz双核处理器,这寓意着平板电脑和笔记本一同进入"多核时代"。
同年6月18日,国内著名B2C---京东在周年庆典之际举行了"隆重"的大规模的促销活动,抢购随之而来,订单挤爆京东 限时达临时取消。 昨天有消费者反映,由于点击量过大,18日早上京东商城网站一度瘫痪。一位消费者说:“18日凌晨1点开始就登不上京东商城。”刘强东也表示:由于流量多次超过4个G,服务器运行缓慢。 昨天,京东商城官网发布公告称,“‘618’活动异常火爆且用户下单速度空前,致使部分用户已购订单显示出现延迟,用户在一段时间内无法在‘我的京东’中查询到自己的订单。目前已购订单显示延迟的问题已得到有效解决,对此给您带来的不便,我们深表歉意。”
2015年05月05日登录风信子网上商城发现,首页除了广告和相关消息外,只有“注册账号获取更多优惠”这唯一一个按钮,没有商品展示,没有产品搜索,不能网上下单,甚至连进入商城的按钮也没有。风信子南沙跨境商品直购体验中心相关负责人表示,这主要是因为预约的人数太多,截至五一,预约人数已超过十万,太多人频繁登陆,导致网店服务器瘫痪,目前技术人员还在努力维修中。该负责人介绍,体验中心的网站目前正在调试,“网站目前的作用主要是给市民预约和提前注册,通过网络注册的市民不用在现场验证身份证等信息,可以提高购买效率。”
下面通过一些实例来认识一下多线程和让大家知道为什么要学习多线程。
写在前面
老板只有两种,好的和坏的。好的老板只跟你谈钱,坏的老板只跟你谈理想。
模拟场景
假设后台有个monitor时事的在监测订单,且可以发现订单然后处理订单,每次(1次/S)可以处理1千个订单,需要向仓库系统发出指令,让他们负责配送发货。那么我们来写一个EmulationSystem(模拟系统)
JobHelper因为我们只是为了模拟一个环境,所以它是这样的。
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() { // 我们的侧重点是多线程,所以我们就模拟每次有1千个订单,而模拟,所以我们不关心其他的。只要订单数量。 return 1000; } /// <summary> /// Submit job /// </summary> /// <param name="jobId">For job id</param> /// <returns>Submit job status</returns> public bool SubmitJob(int jobId) { // 假设针对每个订单向后台发送任务需要1秒,而且每个订单都能成功发送 Thread.Sleep(1000); return true; } } }
背景我们也交待完了,现在需要来得到这些订单信息以后,向仓库submit这些订单。
实战演习
根据这种背景,我们可以有很多处理的办法。
传统办法:
(开启无脑模式:未使用多线程)
代码正文:
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(); // 用一个指示调用线程是否应拥有互斥体的初始所属权的布尔值来初始化 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++) { // 假设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:现在的这些个电商,动不动来个什么周年庆店庆什么双11双12的一顿突突,搞得咱这些老百姓就全部蜂拥而上,显然如果用传统的方法虽然不会出错,但是老板肯定会找你喝茶。在多核时代用这种方法也只能恨铁不成钢了。所以这个方法是绝对不可取的。
Task方法:
如果有对Task不是很熟悉的园友可以在这里解开心谜。
代码正文:
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(); // 用一个指示调用线程是否应拥有互斥体的初始所属权的布尔值来初始化 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(() => { // 假设i就是job id jobHelper.SubmitJob(temp); })); } // 等待所有task执行完毕 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中,可以清楚的发现多线程处理这些频繁交互能力的魅力。你会爱上这个方法。确实提高的效率。但是问题也随之而来,一个进程多线程的总数和大小是有要求的(这个取决于服务器的配置),不是任由我们肆意的开采的。如果订单太多,我们不控制task那是会拉仇恨的。task需要控制。
Semaphore:
如果有对Semaphore不是很熟悉的园友可以在这里解开心谜。
代码正文:
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(); // 用一个指示调用线程是否应拥有互斥体的初始所属权的布尔值来初始化 Mutex 类的新实例。 // 当前进程只能启动一次 mutex = new Mutex(false, "CNBlogs.EmulationSystem"); if (!mutex.WaitOne(0, false)) { Console.Out.WriteLine("Monitor already running..."); return; } // 假设我们的服务器一个进程只能接受的大小=当前线程大小*500 ps:500是设置信号量的最大值 int maxProcNumber = 500; using (semaphoreLimit = new Semaphore(0, maxProcNumber)) { // 以指定的次数退出信号量并返回前一个计数。 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(() => { // 假设i就是job id jobHelper.SubmitJob(temp); // 退出信号量并返回前一个计数。 semaphoreLimit.Release(); })); //} } // 等待所有task执行完毕 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)); } } }
代码效果: