【线程、进程、协程】
学习进程、线程、协程,引申一些内容
为什么要学习进程和线程:
进程和线程目的是为了:提高执行效率
现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。
什么叫“多任务“呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。
我们前面编写的所有的Python程序,都是执行单任务的进程,也就是只有一个线程。如果我们要同时执行多个任务怎么办?
有两种解决方案:
一种是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务。
还有一种方法是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务。
当然还有第三种方法,就是启动多个进程,每个进程再启动多个线程,这样同时执行的任务就更多了,当然这种模型更复杂,实际很少采用。
总结一下就是,多任务的实现有3种方式:
- 多进程模式;
- 多线程模式;
- 多进程+多线程模式。
同时执行多个任务通常各个任务之间并不是没有关联的,而是需要相互通信和协调,有时,任务1必须暂停等待任务2完成后才能继续执行,有时,任务3和任务4又不能同时执行,所以,多进程和多线程的程序的复杂度要远远高于我们前面写的单进程单线程的程序。
因为复杂度高,调试困难,所以,不是迫不得已,我们也不想编写多任务。但是,有很多时候,没有多任务还真不行。想想在电脑上看电影,就必须由一个线程播放视频,另一个线程播放音频,否则,单线程实现的话就只能先把视频播放完再播放音频,或者先把音频播放完再播放视频,这显然是不行的。
Python既支持多进程,又支持多线程,我们会讨论如何编写这两种多任务程序。
小结
线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂。
总之一句话,具体案例具体分析。需要根据实际的情况,精准的定位问题的所在,而不会盲目去做方案
并发 & 并行
并发 : 是指系统具有处理多个任务(动作)的能力
并行 : 是指系统具有 同时 处理多个任务(动作)的能力
并行是不是并发的一个子集
同步 与 异步
同步: 当进程执行到一个IO(等待外部数据)的时候,------等:同步
异步: ------不等:一直等到数据接收成功,再回来处理
任务: IO密集型
计算密集型
对于IO密集型的任务 : python的多线程的是有意义的
可以采用多进程+协程
对于计算密集型的任务: python的多线程就不推荐,python就不适用了。当然了可以用进程,也可以改C
俩种任务为何有不同的针对性,在学习完进程、线程结束之后就会知道为何这样了
进程不是越多越好,线程自然也不是越多越好,具体案例具体分析,请求上下文耗时
进程
概念:就是一个程序在一个数据集上的一次动态执行过程(本质上来讲,就是运行中的程序(代指运行过程),程序不运行就不是进程) 抽象概念
组成:
1、程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成
2、数据集:数据集则是程序在执行过程中所需要使用的资源
3、进程控制块:进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
阐释:进程与进程之间都占用的是独立的内存块,它们彼此之间的数据也是独立的
优点:同时利用多个CPU,能够同时进行多个操作
缺点:耗费资源(需要重新开辟内存空间)
构造方法:
Process([group [, target [, name [, args [, kwargs]]]]])
group: 线程组,目前还没有实现,库引用中提示必须是None;
target: 要执行的方法;
name: 进程名;
args/kwargs: 要传入方法的参数。
实例方法:
is_alive():返回进程是否在运行。
join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。
start():进程准备就绪,等待CPU调度
run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。
terminate():不管任务是否完成,立即停止工作进程
属性:
daemon:和线程的setDeamon功能一样
name:进程名字。
pid:进程号。
创建进程的方式有俩种
一,通过调用模块的方式来创建线程
# 进程模块
import multiprocessing
import time
def f1():
start = time.time()
sum = 0
for n in range(100000000):
sum += n
print(sum)
print("data:{}".format(time.time() - start))
if __name__ == '__main__': # windows在调用进程的时候,必须加这句话,否则会报错
li = []
p1 = multiprocessing.Process(target=f1)
li.append(p1)
p2 = multiprocessing.Process(target=f1)
li.append(p2)
for p in li:
p.start()
for i in li:
i.join()
print("ending...")
二,通过继承类的方式(推荐)
import multiprocessing
class Process(multiprocessing.Process):
def run(self):
sum = 0
for n in range(100000000):
sum += n
print(sum)
li = []
for i in range(2):
p = Process()
li.append(p)
if __name__ == '__main__':
for p in li:
p.start()
for i in li:
i.join()
print("ending")
进程之间的通信
创建进程模块的下队列(Queue)
# 进程之间的通信 Queue from multiprocessing import Queue, Process, Pipe import os,time,random def write(q): print("process to write{}".format(os.getpid())) for value in ["A","B","C"]: print("Put {} to queue...".format(value)) q.put(value) time.sleep(random.random()) def read(q): print("process to read{}".format(os.getpid())) while True: value = q.get(True) print("Get {} from queue".format(value)) if __name__ == '__main__': q = Queue() pw = Process(target=write,args=(q,)) # 这里传输的q是copy的 pr = Process(target=read,args=(q,)) pw.start() pr.start() pw.join() pr.terminate() # 强行终止进程(因为这个子进程定义了一个死循环)