一. 烧水沏茶问题

      在小学四年级有一个烧水沏茶问题,可以作为我们今天讨论话题的引子:

客人来了,要烧一壶茶,但是烧水需要5分钟,洗水壶需要1分钟,洗茶杯需要2分钟,接水需要1分钟,找茶叶需要1分钟,沏茶需要1分钟,问如何使客人能最快地喝上茶?

这是一个简单的时间安排问题,我们来分析一下.

一杯茶需要有什么?杯子,热水,茶叶,这三者都齐了就可以沏茶了,那集齐三者需要哪些步骤?见下图.

如上图所示,烧水最麻烦,需要3个步骤,其中带有人头像的都是需要人参与的.其中烧水的过程是不需要人员参与的,我们可以利用烧水这段时间去做其他的事情.如果以时间轴来描述这件事情,那就如下所示:

答案显而异见,因为我们利用烧水的时间做了另外两件事,节省了时间,所以,最短时间就是8分钟.

以上这个简单的案例可以推广到生活中的方方面面,同时,它也有一个高大上的名字——统筹规划,不过这不是我们这里讨论的主题,按下不表。

既然我们是讲多线程,那让我们把这个案例类比到日常发邮件的问题。

我们对比一下两个案例,会发现,二者的案例都是以人的动作作为时间轴往前推进的,但唯一不同的是,前者的处理中心是人的大脑,后者的处理中心是电脑的CPU。

 二.为什么是多线程?

人类的进步是在发现问题和解决问题的无休止循环中实现的.计算机即是一个典型的例子.

计算机刚开始的时候,是顺序执行的,所有的任务都系于一命,举个例子,当你在使用打印软件打印文档的时候,你只能等计算机完成打印,而不能再输入,也不能另外打开浏览器,如果打印机有1000个文档在排队,虽然打印机是自动打印的,但你可能需要等上一天才可以继续使用计算机,这就浪费了大量的等待时间,这还不是最坏的,最坏的是”串数据”,打印机的数据有可能被其他的程序使用并删除,这时的计算机并不能”同时”做这么多事(一心多用),而是一段时间只能做一件事(一心一用).

进而,windows使用多进程来解决这种问题,进程作为资源分配的最小单位,每个应用程序使用的都是隔离开的资源,不会造成”串数据”的危险.但像前面打印机等待的问题依然存在,因为资源虽然是分开了,但CPU仍然只有一个,如果一个程序占用CPU不撒手,则有可能造成其他的应用程序都使用不了CPU进而无法运行,这样的问题怎么办?

Windows使用线程来解决这个问题,既然多进程的症结在于CPU,线程是对CPU的虚拟,将CPU的使用按时间片划分,每次只能执行一个线程,使用完,切换上下文,执行等待队列的其他线程,这样使得多个任务同时进行,使得我们”好像”拥有多个CPU,这样哪怕一个应用程序无法响应,也不影响其他应用程序的运行,也可以通过更高优先级的”资源管理器”来抢占CPU进而终止无响应的线程.

仔细想想,多线程的思想跟我们烧水沏茶(发邮件)的思路是不是有非常类似的地方,我们把每1分钟当作一个CPU时间片,在烧水(上传附件)的时候,我们使用了另外一个工具烧水器(上传线程),把它作为人能力(CPU处理能力)的扩展.当然,这里,使用工具扩展人的能力是客观存在的,也就是说确实是工具在帮助人类做事,但CPU只是因为处理速度极快,他不断地切换时间片来处理多个任务,在外部看起来,好像多个任务同时被处理,正如电影每秒24帧看起来非常流畅没有中断一样.从表面上来看,这使得人类(电脑)拥有了同时处理多个事务的能力.

那windows给我们提供了怎样的多线程?它通过CLR暴露出来给我们的接口有哪些?这些具像的问题,就不是我们这篇文章所讨论的了.

 三.多线程面临的基本问题

我们知道,windows发展到现在,最终选择了多线程,但是多线程也不是最终的解决方案,使用它仍然会出一些问题,而在没有发展出更好的解决方案之前,防止这些问题的终极解决方案就是各位程序员写出高质量的多线程程序.

那么,多线程面临着什么基本问题?

1.资源争用(资源同步)

我们知道,进程是分配资源的最小单位,线程是CPU调度的最小单位.也就是说,多线程中,多个线程共享进程的资源,举一个适合吃货的例子:大家在一个桌子上吃饭,共用一个汤勺,如果你想喝汤,我也想喝汤,那么,谁先谁后?桌子上的菜和餐具都是资源,汤勺也是资源.

也许,有的同学说,这个好办,先来后到,谁先拿了汤勺就谁喝汤,剩下的人员排队就好了.对,这是一个好办法,也是资源争用的一个基本解决思路.

现实生活,我们知道的概念,但程序不知道如何,我们用锁机制来实现的机制,当我们拿到汤勺的时候,我们告诉其他人,”这个汤勺我目前占用了”,那别人就知道要等我用完了,再来取汤勺.

2.死锁

锁机制又会带新的问题,这里假设,桌子上的汤碗不够,需要多人共用汤碗,这时,张三拿着汤勺等李四的汤碗,李四拿着汤碗等着张三的汤勺,双方都占着这些东西不撒手,陷入无限等待的循环,这就造成了谁也喝不了汤的窘境.而这,也就是锁机制带来的问题——死锁.

死锁怎么解决?只有靠程序员,这就是我们今天讨论多线程的意义所在。

总的来说,多线程带来的基本问题就是资源争用,我们用锁机制来解决资源争用的问题,但又会带来死锁的可能。因而,在使用多线程的时候,我们要用锁,但要防止死锁。

其实,多线程的细节问题(非基本问题)远不止这些,下面列举一二:

◎前面我们在讲到烧水沏茶的时候,如果烧水、沏茶、洗杯子、找茶叶,是4个人分别在做,那沏茶的人需要等待其他3个人都完成后才能沏茶,他如何得知3个人都完成了任务呢?那3个人是如何通知他的呢?

◎如果客人突然要走了,那么如何取消这个沏茶行动?

◎前面我们说到汤勺问题的时候,如果我想得到汤勺,如果一段时间得不到,是不是我就要非喝这个汤不可呢?我可不可以放弃,换个面吃吃?

◎前面汤勺问题中,如果我们是两个桌子(进程)共同一个汤勺,那么两个桌子如何沟通汤勺的问题?如何占用汤勺?

像这样的问题在多线程中是非常多的,前面我们说过,锁可以用来解决资源争用的问题,而像这些各种情形下的争用问题,.NetFramework的解决方案就是各式各样的锁配合各种线程类。比如,我们可以使用互斥锁来实现多线程同步的问题,可以使用超时、等待机制来避免无限等待,可以使用通知机制来实现多任务的合并,可以使用取消机制来实现线程的取消,可以使用父子任务来实现任务的层次关系,可以使用进程互斥锁来实现进程间的同步.而这些都是我们需要在多线程中需要了解并掌握的.

四.推荐两篇文章

 pansz在知乎的回答 https://www.zhihu.com/question/19901763

阮一峰 http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

C#夯实基础之多线程一:初识多线程的更多相关文章

  1. java基础之多线程一:概述

    概述: 进程有多条执行路径, 合成为: 多线程. 进程和线程的描述: 进程: 可执行程序(文件), 例如: .exe//可以把进程理解为一辆车. 一台电脑上可以有多个进程, 这些进程之间的数据是相互隔 ...

  2. C#夯实基础之多线程二:主线程、前台线程与后台线程

    我们在<C#夯实基础之多线程一:初识多线程>一文中第二部分中指出,既然windows最终发展出了多线程模型,按理说,我们直接使用一个.NetFramework的线程类就可以直接撸代码了,但 ...

  3. Java基础-进程与线程之Thread类详解

    Java基础-进程与线程之Thread类详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.进程与线程的区别 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 ...

  4. SQL夯实基础(九)MySQL联接查询算法

    书接上文<SQL夯实基础(八):联接运算符算法归类>. 这里先解释下EXPLAIN 结果中,第一行出现的表就是驱动表(Important!). 对驱动表可以直接排序,对非驱动表(的字段排序 ...

  5. Linux基础入门(一)初识Shell

    Linux基础入门(一)初识Shell shell是什么 Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Shell ...

  6. 夯实基础系列四:Linux 知识总结

    前言 前三节内容传送门: 夯实基础系列一:Java 基础总结 夯实基础系列二:网络知识总结 夯实基础系列三:数据库知识总结 现在很多公司项目部署都使用的是 Linux 服务器,互联网公司更是如此.对于 ...

  7. 夯实基础上篇-图解 JavaScript 执行机制

    讲基础不易,本文通过 9 个 demo.18 张 图.2.4k 文字串讲声明提升.JavaScript 编译和执行.执行上下文.调用栈的基础知识.

  8. Servlet基础(三) Servlet的多线程同步问题

    Servlet基础(三) Servlet的多线程同步问题 Servlet/JSP技术和ASP.PHP等相比,由于其多线程运行而具有很高的执行效率. 由于Servlet/JSP默认是以多线程模式执行的, ...

  9. delphi 线程教学第一节:初识多线程

    第一节:初识多线程   1.为什么要学习多线程编程?   多线程(多个线程同时运行)编程,亦可称之为异步编程. 有了多线程,主界面才不会因为耗时代码而造成“假死“状态. 有了多线程,才能使多个任务同时 ...

随机推荐

  1. jQuery Ajax传值给Servlet,在Servlet里Get接受参数乱码的解决方法

    最近在学jquery ui,在做一个小功能的时候需要将前台的值获取到,通过Ajax传递给Servlet,然后再在返回数据结果,但是在Servlet接受参数的时候,通过后台打印,发现接受乱码,代码示例如 ...

  2. csv表格处理(上)-- JS 与 PHP 协作导入导出

    CSV简介 在开发后台管理系统的时候,几乎无可避免的会遇到需要导入导出Excel表格的需求.csv也是表格的一种,其中文名为“逗号分隔符文件”.在Excel中打开如下图左边所示,在记事本打开如下图右边 ...

  3. iOS学习-创建带下划线的button

    UIButton *tempBtn = [UIButton buttonWithType: UIButtonTypeCustom]; tempBtn.frame = CGRectMake(, , , ...

  4. Apache的dbutils的架构图

  5. Docker命令详解

    Docker命令详解   最近学习Docker,将docker所有命令实验了一番,特整理如下: # docker --help Usage: docker [OPTIONS] COMMAND [arg ...

  6. MyEclipse10--的使用经验

    MyEclipse10--的使用经验总结 ------------------ 1.MyEclipse中的验证validation----->>用MyEclipse做ExtJs项目研发的时 ...

  7. radio 切换内容

    <!DOCTYPE html><html><head> <meta charset=utf-8 /> <title>test</tit ...

  8. Visual SVN 5.01 Po jie 笔记

    最近搞项目要与几个同事一起coding,鉴于代码的合并和提交的问题,所以要搞个版本管理.由于是私有的项目,所以退git 求SVN了.装了乌龟和Visual SVN,才发现Visual SVN的客户端不 ...

  9. Bubble Cup 8 finals I. Robots protection (575I)

    题意: 有一个正方形区域, 要求支持两个操作: 1.放置三角形,给定放置方向(有4个方向,直角边与坐标轴平行),直角顶点坐标,边长 2.查询一个点被覆盖了多少次 1<=正方形区域边长n<= ...

  10. C语言基础(10)-数组

    一.数组的定义 数组就是在内存中连续的相同类型的变量空间. 二.数组在内存中的存储方式 同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的,数组名是一个地址的常量,代表数组中 ...