一、操作系统提供的多进程
1。单线程单进程:一个人在一个桌子上吃菜。
2。多线程单进程:多个人在同一个桌子上一起吃菜。
3。单线程多进程:多个人每个人在自己的桌子上吃菜。
多线程的问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了。。。此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说资源共享就会发生冲突争抢。
1。对于 Windows 系统来说,【开桌子】的开销很大,因此 Windows 鼓励大家在一个桌子上吃菜。因此 Windows 多线程学习重点是要大量面对资源争抢与同步方面的问题。
2。对于 Linux 系统来说,【开桌子】的开销很小,因此 Linux 鼓励大家尽量每个人都开自己的桌子吃菜。这带来新的问题是:坐在两张不同的桌子上,说话不方便。因此,Linux 下的学习重点大家要学习进程间通讯的方法。
开桌子的意思是指创建进程。开销这里主要指的是时间开销。
可以做个实验:创建一个进程,在进程中往内存写若干数据,然后读出该数据,然后退出。此过程重复 1000 次,相当于创建/销毁进程 1000 次。在我机器上的测试结果是:
UbuntuLinux:耗时 0.8 秒
Windows7:耗时 79.8 秒
两者开销大约相差一百倍。
这意味着,在 Windows 中,进程创建的开销不容忽视。换句话说就是,Windows 编程中不建议你创建进程,如果你的程序架构需要大量创建进程,那么最好是切换到 Linux 系统。
大量创建进程的典型例子有两个,一个是 gnu autotools 工具链,用于编译很多开源代码的,他们在 Windows 下编译速度会很慢,因此软件开发人员最好是避免使用 Windows。另一个是服务器,某些服务器框架依靠大量创建进程来干活,甚至是对每个用户请求就创建一个进程,这些服务器在 Windows 下运行的效率就会很差。这"可能"也是放眼全世界范围,Linux 服务器远远多于 Windows 服务器的原因。
再次补充:如果你是写服务器端应用的,其实在现在的网络服务模型下,开桌子的开销是可以忽略不计的,因为现在一般流行的是按照 CPU 核心数量开进程或者线程,开完之后在数量上一直保持,进程与线程内部使用协程或者异步通信来处理多个并发连接,因而开进程与开线程的开销可以忽略了。
另外一种新的开销被提上日程:核心切换开销。
现代的体系,一般 CPU 会有多个核心,而多个核心可以同时运行多个不同的线程或者进程。
当每个 CPU 核心运行一个进程的时候,由于每个进程的资源都独立,所以 CPU 核心之间切换的时候无需考虑上下文。
当每个 CPU 核心运行一个线程的时候,由于每个线程需要共享资源,所以这些资源必须从 CPU 的一个核心被复制到另外一个核心,才能继续运算,这占用了额外的开销。换句话说,在 CPU 为多核的情况下,多线程在性能上不如多进程。
因而,当前面向多核的服务器端编程中,需要习惯多进程而非多线程。
二、单个程序内的多线程
多线程使得程序内部可以分出多个线程来做多件事情,而不会造成程序界面卡死。比如迅雷等多线程下载工具就是典型的多线程。一个下载任务进来,迅雷把文件平分成10份,然后开10个线程分别下载。这时主界面是一个单独的线程,并不会因为下载文件而卡死。而且主线程可以控制下属线程,比如某个线程下载缓慢甚至停止,主线程可以把它强行关掉并重启另外一个线程。
另外就是一些程序的打印功能,比如记事本、Adobe Reader,打印的时候就只能打印,无法在主界面进行操作,而Word就有“后台打印”的功能,点了打印命令之后,还可以回到主界面进行修改、保存等操作。
多线程和多进程的区别。平常指的多进程是操作系统下同时运行多个进程,比如Word和Excel同时打开,并且可以并行地同时执行一些操作。这种多进程和多线程没什么好比较的。可以比较的是同一个程序里的多线程和多进程。
多线程因为在同一个进程里,所以可以共享内存和其他资源,比如迅雷里10个线程一齐下载一个文件,这个文件是由进程打开的,然后10个线程都可以往里写入东西。如果是10个进程就不行了,操作系统不允许一个文件由两个进程同时写入。
另外,Chrome就是一个典型的多进程程序,里面每个标签页、扩展、插件都是单独的进程,各自独占资源,相互隔离,一个进程出错死掉只会影响一个页面或者插件,再也不会出现Flash插件出错崩溃导致整个浏览器崩溃的情况了。
使用多线程一般有两个不同的目的:
一是把程序细分成几个功能相对独立的模块,防止其中一个功能模块阻塞导致整个程序假死(GUI程序是典型)
另一个就是提高运行效率,比如多个核同时跑,或者单核里面,某个线程进行IO操作时,另一个线程可以同时执行。
三 详解
为了讲清楚多线程编程,这儿先讲一个场景,
这个场景是这样子的,山上有座庙(进程),庙里住了很多小和尚和老和尚(线程),当然隔壁山上也有很多尼姑妹子。小和尚和老和尚们每天都需要下山挑水喝。这些个和尚有刚上山的(就是有点傻的),有比较聪明的,也有闹过矛盾的。场景,人物呢,大概就是这么个样子。
俗话说的好,有人的地方就有江湖。这些个小和尚,老和尚都不是很安分,为了每天的这个挑水工作闹了很多很多的矛盾,也犯过很多傻。
下面对挑水工作所有发生的情况做个详述:
1)刚上山的小和尚挑水,就是比较傻的那个,和别人都不认识,每天挑水都自己一个人,一次挑一桶水,一天下来发现挑的水根本不够喝。(串行)
2)和尚里面有个力气大的,和别人关系不好,每次挑水能挑三桶左右,一天下来,发现挑的水勉强够喝。 (并发)
3)和尚里面有几个关系好的,力气也比较大,每次挑水都一起去,每个人都能挑两桶左右,一天下来,发现挑的水不仅够喝,还有时间帮隔壁山尼姑妹子挑水。 (并行)
4)在水井里打水,有个专门的妹子负责打水,比较傻的那个小和尚每次都盯着打水的妹子,还随时随地看水桶装满没有(同步);当然,有傻的,也有聪明的,这个聪明的和尚在妹子打水的时候,就乘机在一旁休息,水打满以后,妹子都过来告诉他。(异步)
5)水井里有一天没水了,傻和尚看见水井里没水以后,在水井边上一直等着,等有水了再挑回去(阻塞);聪明和尚呢,看见水井里没水了,一点都不犹豫的回去了。(非阻塞)
6)又有一天,那闹过矛盾的两个和尚挑水,正好狭路相逢,谁也不让谁过去,一直僵持着。(死锁)
7)寺庙里厨房空间有限,每次只能进去一定数量的和尚,当厨房满了以后,其他人只能在外面等着,直到里面的人出来。这个实现的方法是,厨房门口挂了很多锁,每个人进去以后都拿走一把锁,出来以后把锁挂上,这样外面的人就可以知道厨房里面人满没满。(信号量)
终于编完了。。。
通过上面生动形象的描述,这个线程里面的一些概念已经明白了。
下面解释下多线程。
还是上面的例子,在这个寺庙里面,如果每天都由那个新来的傻子和尚挑水喝,肯定是不行的,有的喝没得喝咱且不说,你让他累死了咋整。何况寺庙里那么些个空着的水桶那么些个不干活的人闲着也是闲着。
所以多线程的意思就是多个线程一起去协同完成一个任务,通过充分去共享资源来达到提升效率的一种编程思想。
当然,在这个过程中会遇到很多麻烦,比如会碰上死锁的问题,同时去争抢同一个资源的问题等等。
为了解决这个问题,iOS里是使用加锁这个方法避免,当然,使用锁,会在一定的程度上印象执行的效率,我们需要在效率和安全之间寻找一个平衡点。