性能:在其他同等条件下,高性能的程序应该可以等同于CPU的利用率,CPU的利用率越高(一直在工作,没有闲下来的时候),程序的性能越高.
体验:这里的体验不只是界面多么漂亮,功能多么顺手,这里的体验指程序的响应速度,响应速度越快,用户体验越好.
由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法.
另一种解决IO问题的方法是异步IO.当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了. 一段时间后,当IO返回结果时,再通知CPU进行处理,在处理 IO 的时候,阻塞和非阻塞都是同步 IO. 只有使用了特殊的 API 才是异步 IO.
首先对一些名词的理解
被调用方:同步与异步关乎做事情的方式. 同步:做完一件事再去做另一件. 异步:同时做多件事情,某个事情有结果了再去处理(又一个新事情)我的理解: 被调用方有没有能力同时处理问题的能力以及回调的功能 同步和异步关注的是消息通信机制(synchronous communication/ asynchronous communication): 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回,但是一旦调用返回,就得到返回值了 换句话说,就是由调用者主动等待这个调用的结果
而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果.换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果. 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用
典型的异步编程模型比如Node.js1
2
3
4
5
6
7
举个通俗的例子: 你打电话问书店老板有没有《分布式系统》这本书
如果是同步通信机制,书店老板会说,你稍等,"我查一下" ,
然后开始查啊查,等查好了(可能是5 秒,也可能是一天)告诉你结果(返回结果).
而异步通信机制,书店老板直接告诉你我查一下啊,
查好了打电话给你,然后直接挂电话了(不返回结果).
然后查好了,他会主动打电话给你.在这里老板通过"回电" 这种方式来回调.
回调异步1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import time
import threading
def long_io (callback) :
"""将耗时的操作交给另一线程来处理"""
def fun (cb) :
"""耗时操作"""
print( "开始执行IO操作" )
time.sleep(5 )
print("完成IO操作,并执行回调函数" )
cb("io result" )
threading._start_new_thread(fun, (callback,))
def on_finish (ret) :
"""回调函数"""
print( "开始执行回调函数on_finish" )
print( "ret: %s" % ret)
print( "完成执行回调函数on_finish" )
def req_a () :
print( "开始处理请求req_a" )
long_io(on_finish)
print( "离开处理请求req_a" )
def req_b () :
print( "开始处理请求req_b" )
time.sleep(2 )
print( "完成处理请求req_b" )
def main () :
req_a()
req_b()
while 1 :
pass
if __name__ == '__main__' :
main()
调用方:阻塞与非阻塞关乎如何对待事情产生的结果.关注的是程序在等待调用结果(消息,返回值)时的状态. 阻塞调用是指调用结果返回之前,当前线程会被挂起.调用线程只有在得到结果之后才会返回. 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程 阻塞:不等到想要的结果我就不走了. 非阻塞:有结果我就带走,没结果我就空手而回,总之一句话:爷等不起
1
2
3
4
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己"挂起"
直到得到这本书有没有的结果,如果是非阻塞式调用
你不管老板有没有告诉你,你自己先一边去玩了
当然你也要偶尔过几分钟check一下老板有没有返回结果
1
2
3
4
5
6
7
8
9
10
11
老张爱喝茶,废话不说,煮开水. 出场人物:
老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶).
1 老张把水壶放到火上,立等水开.(同步阻塞) 老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有.(同步非阻塞)
老张还是觉得自己有点傻,于是买了把会响笛的那种水壶.水开之后,能大声发出嘀~~~~的噪音.
3 老张把响水壶放到火上,立等水开.(异步阻塞)
老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶.(异步非阻塞)
并发与并行 并发指在同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行(纳秒级),使得在宏观上具有多个进程同时执行的效果. 只能运行一个进程,CPU不断地在这些进程之间轮换执行,
并行指在同一时刻,有多条指令在多个处理器上同时执行;多个CPU处理器
进程(拥有独立的内存单元,chrome使用多进程)与线程(也被称作轻量级进程) 进程:程序的一次执行. 线程:CPU的基本调度单位 ,而在Linux系统里面,在最底层,线程和进程确实是不区分的.
进程是系统进行资源分配和调度的一个独立单位,线程是进程的执行单元,在进程中是独立的、并发的执行流线程的调度和管理,由进程本身负责完成.
当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程.当一个程序进入内存,运行后,即变成一个进程.
进程的三个特性
独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间.在没有经过进程本身允许的情况下,一个用户进程不可以直接就访问其他进程的地址空间.
动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合.在进程中加入了时间的概念.进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的.
并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响
车间:
一个进程与线程的一个简单解释 程序(车间,有独立的内存空间)之间进行内存数据交换(通信)比较麻烦:每个程序的内存空间都是被保护的,不能被别的程序直接访问,所以要通过某种介质,管道或第三方工具,去通信. nosql(第三方):将内存的数据缓存进来,供其它的程序调用,所有的程序都可以往里存数据,所有的程序都可以取数据.相当于实现了一个共享的内存空间
餐桌:
一群人(多个线程)在一个桌子(进程)上吃饭,他们会涉及到一些问题,比如多个人可能会夹一个菜(竞争) A和B同时看到盘子里面有一块肉,同时伸出筷子去夹,A先夹走,B迟了一点伸到盘子的时候已经没了,只能缩回来(临界资源,互斥) 有一个点心需要用馍夹肉一起吃.A夹了肉,B夹了馍,A需要B的馍,B需要A的肉,他们僵持不下谁都不让步(死锁). 多线程之间的资源共享是非常方便的,因为他们共用进程的资源空间(在一个桌子上),但是需要注意一系列的问题,竞争,死锁,同步等
如果在旁边再开一个桌子(进程). 那么桌子之间讲话,递东西又不方便(进程间通信),而开一个桌子的开销比在一个桌子上多加一个人的开销要大. 另外一个桌子上的人数不可能无限制增加,桌子的容量有限也坐不下这么多人(进程的线程句柄是有限制的). 一个桌子坏了不会影响到另一个桌子上面人的就餐情况(进程间相互独立,一个进程崩溃不会影响另一个),而一个桌子上的某人喝挂了需要送医院, 估计这一桌人都要散了(线程挂掉会导致整个进程也挂掉).所以多线程与多进程是各有优缺点,不能一概而论
对于 Windows系统来说,【开桌子】的开销很大,因此Windows鼓励大家在一个桌子上吃菜.因此Windows多线程学习重点是要大量面对资源争抢与同步方面的问题.
对于Linux系统来说,【开桌子】的开销很小,因此Linux鼓励大家尽量每个人都开自己的桌子吃菜.这带来新的问题是:坐在两张不同的桌子上,说话不方便. 因此,Linux下的学习重点大家要学习进程间通讯的方法.
单线程(单任务,按顺序去完成)和多线程 多线程则扩展了多进程的概念,使得同一个进程可以同时并发(轮换执行)处理多个任务.线程(Thread)也被称作轻量级进程(LightweightProcess),线程是进程的执行单元.
就像进程在操作系统中的地位一样,线程在进程中是独立的、并发的执行流.进程操作系统–线程进程,当线程被初始化后,主线程就被创建了对于应用程序而言,
通常至少有一个主线程,可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每条线程也是相互独立的;
线程是进程的的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程,线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量, 但是不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源.
因为多个线程共享父进程里的全部资源,因此编程更加方便;但必须更加小心,必须确保线程不会妨碍同一进程里的其他线程.线程的调度和管理由进程本身负责完成.
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
车间:
当要完成一项工作或多项工作时(但还是在一个主线程中),单线程就好比一个工人,而多线程就是招了多个工人一起干活,效率当然快了! 但管理就费劲了(死锁,竟争),允许单个任务分成不同的部分运行
餐桌:
单线程就是整个餐厅只有一个单人桌,这个人吃完了,下一个人轮上. 但大餐馆用的可能是八仙桌,同时能容纳八个人吃饭,这就是多线程:从一次一个变成了一次多个或者多次多个
多线程的作用 1.把程序细分成几个功能相对独立的模块,防止其中一个功能模块阻塞导致整个程序假死(GUI程序是典型).一个线程一件事,多线程做多件事
2.提高运行效率,比如多个核同时跑,或者单核里面,某个线程进行IO操作时,另一个线程可以同时执行 这样一条主线程,处理整个程序任务的主方向的链,而其链上又有许许多多的分支,就像树枝那样, 这样,既有了主线程去处理那些主要任务,又有了那些细小线程去处理耗时费力任务,从而让界面看起来更加流畅
多线程做同一件事 多线程使得程序内部可以分出多个线程来做多件事情,而不会造成程序界面卡死.比如迅雷等多线程下载工具就是典型的多线程. 一个下载任务进来,迅雷把文件平分成10份,然后开10个线程分别下载.这时主界面是一个单独的线程,并不会因为下载文件而卡死. 而且主线程可以控制下属线程,比如某个线程下载缓慢甚至停止,主线程可以把它强行关掉并重启另外一个线程.
多线程因为在同一个进程里,所以可以共享内存和其他资源,比如迅雷里10个线程一齐下载一个文件,这个文件是由进程打开的, 然后10个线程都可以往里写入东西.如果是10个进程就不行了,操作系统不允许一个文件由两个进程同时写入
python中使用 GIL(global interpreter lock)保证了线程安全(保证数据被安全读取),即同时只能有一个线程在CPU上运行, 这个线程也不能扩散到其它CPU上,GIL是以CPU为单位去控制这个锁,对于python的多线程,多核也没有实际的提速作用,
所以多线程在Python中只能跑在一个CPU上.如果想利用多CPU的话,就使用多进程(需要给独立的内存空间,所以比较占内存资源). 当每个CPU核心运行一个进程的时候,由于每个进程的资源都独立,所以CPU核心之间切换的时候无需考虑上下文.
当每个CPU核心运行一个线程的时候,由于每个线程需要共享资源,所以这些资源必须从CPU的一个核心被复制到另外一个核心,才能继续运算,这占用了额外的开销. 换句话说,在CPU为多核的情况下,多线程在性能上不如多进程.
(1)以多进程形式,允许多个任务同时运行; (2)以多线程形式,允许单个任务分成不同的部分运行; (3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源. 进程与线程的区别 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口.但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制, 但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配
进程和线程的主要差别在于它们是不同的操作系统资源管理方式.进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径.线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮
参考于此