菜鸟备忘录[通识]——线程&进程;并行&并发;同步&异步and阻塞&非阻塞 必看必晕

来源:互联网 发布:月下的妖樱阿波罗数据 编辑:程序博客网 时间:2024/05/29 09:33

前言

这些概念一直模糊很久,期初第一次接触是在《Java核心技术Ⅰ》中,看着密密麻麻的文字和变化多端的代码,一直懵逼敬畏到现在。最近在学node.js,也在想深入python爬虫,于是深知,该来的还是会来的。硬着头皮去问了百度老师,于是有了本文的备忘录(不生产文章,我是文章的搬运工)。

线程&进程

搬运于知乎:
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 为多核的情况下,多线程在性能上不如多进程。因而,当前面向多核的服务器端编程中,需要习惯多进程而非多线程。

以上举了个简单通俗的栗子,如果有想深入了解一探究竟刨根问底求知若渴的同学,可以移步这里:传送门

并行&并发

又来个简单易懂通俗的例子:
假如你在看电视,这个时候电话响了,你会有以下几种选择:
1.看完电视再接电话——>不支持并发也不支持并行
2.先去接电话,接完继续看电视——>支持并发
2.一边看电视一边接电话——>支持并行

总结:并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
它们最关键的点就是:是否是『同时』。
深入:
如果某个系统支持两个或者多个动作(Action)同时存在,那么这个系统就是一个并发系统。如果某个系统支持两个或者多个动作同时执行,那么这个系统就是一个并行系统。并发系统与并行系统这两个定义之间的关键差异在于“存在”这个词。在并发程序中可以同时拥有两个或者多个线程。这意味着,如果程序在单核处理器上运行,那么这两个线程将交替地换入或者换出内存。这些线程是同时“存在”的——每个线程都处于执行过程中的某个状态。如果程序能够并行执行,那么就一定是运行在多核处理器上。此时,程序中的每个线程都将分配到一个独立的处理器核上,因此可以同时运行。我相信你已经能够得出结论——“并行”概念是“并发”概念的一个子集。也就是说,你可以编写一个拥有多个线程或者进程的并发程序,但如果没有多核处理器来执行这个程序,那么就不能以并行方式来运行代码。因此,凡是在求解单个问题时涉及多个执行流程的编程模式或者执行行为,都属于并发编程的范畴。摘自:《并发的艺术》 — 〔美〕布雷谢斯在豆瓣阅读书店查看:https://read.douban.com/ebook/10034459/

并发:交替做不同事的能力
并行:同时做不同事的能力
行话解释:
并发:不同代码块交替执行的性能
并行:不同代码块同时执行的性能

传送门:http://ifeve.com/talk-concurrency/

同步&异步and阻塞&非阻塞

先来看两个知乎上的小故事:

1

老张爱喝茶,废话不说,煮开水。出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。1 老张把水壶放到火上,立等水开。(同步阻塞)老张觉得自己有点傻2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。3 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。所谓同步异步,只是对于水壶而言。普通水壶,同步;响水壶,异步。虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。所谓阻塞非阻塞,仅仅对于老张而言。立等的老张,阻塞;看电视的老张,非阻塞。情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。——来源网络,作者不明。

2

10年前,还没有12306的年代,大家买票只能去火车站买。因为大家都要过年回家,都还不想等,火车站只有一个,窗口只有那么多,头疼啊。更头疼的是,排到窗口的那个人,各种挑剔,不要贵的,不要晚上的,不要站票……跟售票员各种墨迹,后面的人更加着急,一个个义愤填膺,骂爹骂娘。现在假设整个城市就只有1个火车,1个售票员,每个乘客咨询售票员后需要思考1分钟再决定买哪趟车的票。
1.异步 :在买票的人咨询后,需要思考1分钟,马上靠边站,但不用重新排队,什么时候想清楚可以立马去跟售票员去买票。在该人站在旁边思考的时候,后面的人赶紧上去接着买。这时候队伍是很快的挪动的,没有阻塞,售票员的最大化的效率。
2.多线程:火车站开n个窗口(但还是只有一个人售票),外面同时排n个队,售票员回答咨询者问题后,立马马上去下个窗口,然后继续轮换到下个窗口…..哪个窗口的人决定好了,售票员立马过去买给他。这个时候乘客比较简单,但万一那个队伍有人思考半天纠结,后面的人就悲剧了。
3.并行:复制n个火车站,同时卖票,买票能力大大增强。大家也可以哪个火车站人少,就去那个买票。可见:在只有一个火车站,且只有一个售票员的情况下,卖完一个再卖一个就会导致资源浪费,效率低下,队伍卡死,很难往前挪动。1,2优化的办法都解决了队伍不动,售票率低下的问题。但增加火车站,增加窗口,增加售票员才是好办法。

结论:
1.异步和多线程其实效率差不多,但是开的窗口不多例如3个,同时有很多人都是去花5分钟,而不是1分钟去纠结的时候,多线程效率实际是低于异步的,因为售票员还是常遇到3个队伍同时卡在那纠结不能买票的时候。
2. 这2个概念拿来对比也有点不合适,因为他们不是一个概念,多线程的目的还是为了实现异步,多线程应该是一种实现异步的手段。异步应该去跟同步比较才对。
3. 多线程比较简单,但需要增设窗口,增加成本,且售票员比较累这类似apache下php,和node.js下javascript的关系,一个是多线程,但是是阻塞的,另外一个是单线程异步非阻塞的。php的方案比较符合常规思维,但比较费内存,node.js非阻塞,用较少的资源就能完成同样的任务,但编程比较费神。
4.并行,类似同时利用多核cpu的各个核去计算。并发可分为伪并发、真并发。前者例如单核处理器的并发,后者发是指多核处理器的并发。
5.终极办法是并行计算,并且每个cpu下进行异步计算,这样你的每个核都充分利用。只不过对编程要求太高了太高了,如果不是密集型计算,例如大型有限元计算(多采用并发),或者服务器同时处理上千的访问(多采用异步或者多线程),还是老老实实的用传统的办法吧,毕竟常规程序的计算量对现在的硬件来说,问题都不大。

搬运知乎总结:

A.概念
阻塞非阻塞:
请求不能立即得到应答,需要等待,那就是阻塞;否则可以理解为非阻塞。

同步异步:
某业务需要甲乙甚至多方合作的时候,总是按照“甲方请求一次,乙方应答一次”这样的有序序列处理业务,只有当“一次请求一次应答”的过程结束才可以发生下一次的“一次请求一次应答”,那么就说他们采用的是同步。(同步IO中,对同一个描述符的操作必须是有序的)如果甲方只要有需要,就会发送请求,不管上次请求有没有得到乙方应答。而乙方只要甲方有请求就会接受,不是等这次请求处理完毕再接受甲方新请求。这样请求应答分开的序列,就可以认为是异步。异步情况下,请求和应答不需要一致进行,可能甲方后请求的业务,却先得到乙方的应答。同步是线性的,而异步可以认为是并发的。(异步IO中,异步IO可以允许多方同时对同一个描述符发送IO请求,或者一次发多个请求,当然有机制保证如何区分这些请求,)举个例子:我去买一本书,立即买到了,或者没有就走了,这就是非阻塞;(编程中设置IO成非阻塞,返回后再去检查描述符,或者等待通知,然后再去读取。相当于老板告诉我可以先忙点别的,过一会再来问问,或者老板通知我。但期间这个窗口(文件描述符)别人是用不了的)(“立即买到了”在IO中也需要等待,不能算非阻塞IO)如果恰好书店没有,我就等一直等到书店有了这本书买到了才走,这就是阻塞;而排在我后面的人呢只有我买到了书后才能再买书了。如果书店恰好没有,我就告诉书店老板,书来了告诉我一声让我来取或者直接送到我家,然后我就走了,去做别的事了,这就是异步。这时候如果很多人来买书,都是老板登记一下完事。 (从IO角度来说,“告诉我来取”,这个近似于信号驱动IO,不能算异步IO。必须书送到我家才算是异步,如果不送到我家,我想看这本书之前,终究还是需要我跑一趟)前面两种情况,非阻塞和阻塞都可以称为同步。反映在编程方面就是 用户进程 调用 系统调用。(用户进程对应我,内核 对应 书店老板,书对应数据资源data , 买书就是一个系统调用了,其中内核拷贝数据到进程这个过程近似于老板送书到我手中)。B. 在同步异步IO概念中,同步异步的不同在于,针对同一个描述符上的IO操作,从IO操作发起 到 得到 IO结果 这个过程而言,总是按照“发起请求,得到结果”这个有序序列进行的,这样便有了最小的等待这种情况:读取时,确知IO有数据,但需要等待内核拷贝数据到进程空间。这个最小情况的等待,同步IO都有。unix网络编程中将IO模型分为5类:阻塞IO,非阻塞IO,IO复用,信号驱动,异步IO。阻塞IO就是那种recv, read,一直等,等到有了数据才返回;非阻塞IO就是立即返回,设置描述符为非阻塞,但是要进程自己一直检查是否可读;IO复用其实也是阻塞的,不过可以用来等很多描述符,比起阻塞有了进步,可以算有点异步了,但需要阻塞着检查是否可读。对同一个描述符的IO操作也是有序的。信号驱动采用信号机制等待,有了更多的进步,不用监视描述符了,而且不用阻塞着等待数据到来,被动等待信号通知,由信号处理程序处理。但对同一个描述符的IO操作还是有序的。异步IO,发送IO请求后,不用等了,也不再需要发送IO请求获取结果了。等到通知后,其实是系统帮你把数据读取好了的,你等到的通知也不再是要求你去读写IO了,而是告诉你IO请求过程已经结束了。你要做的就是可以处理数据了。且同一个描述符上可能同时存在很多请求。(对应上面那个买书例子中,就是送书到我家,我直接看书就行了,不需要再去跑一趟了)。其中IO服用和信号驱动,在处理业务逻辑上可以说有异步,但在IO操作层面上来说还是同步的。posix.1严格定义的异步IO是要求没有任何一点阻塞,而上述的前面四个(阻塞IO,非阻塞IO,IO复用,信号驱动)都不同程度阻塞了,而且都有一个共同的阻塞: 内核拷贝数据到进程空间的这段时间需要等待。 (所以上面的举例中: 必须要书送到我家,否则都不算异步,纠结。。。)
例子暂时就那么多,有没看爽的小伙伴可以看一些关于这些方面的书籍,关于不同语言的不同介绍,以后的笔记再详细记录。

阅读全文
0 0
原创粉丝点击