本篇文章目录
一、多任务编程
1、什么叫多任务
2、单核CPU如何实现多任务
3、多核CPU如何实现多任务
二、多进程编程
1、进程的创建
2、多进程编程
3、进程间通信
三、多线程编程
1、什么是线程
2、线程和进程的区别
3、多线程编程
4、GIL全局解释器锁
5、线程同步和线程锁
6、死锁
四、协程
1、什么是协程
2、协程的优势
3、线程的实现
五、总结
一、多任务编程
1、多任务的概念?
现实⽣活中的多任务:有很多的场景中的事情是同时进⾏的,⽐如开⻋的时候 ⼿和脚共同来驾驶汽⻋,再⽐如唱歌跳舞也是同时进⾏的。
即就是操作系统可以同时运⾏多个任务。打个 ⽐⽅,你⼀边在⽤浏览器上⽹,⼀边在听MP3,⼀边在⽤Word赶作业,这就是多任务,⾄少同时有3个任务正在运⾏。还有很多任务悄悄地在后台同时运 ⾏着,只是桌⾯上没有显示⽽已。
现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
——答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
2、单核CPU如何实现“多任务?
操作系统轮流让各个任务交替执⾏,每个任务执⾏0.01秒,这样反复执⾏下去。 表⾯上看,每个任务交替执⾏,但CPU的执⾏速度实在是太快了,感觉就像所有任务都在同时执⾏⼀样。
三个执行实例A,B,C在单个CPU上交替执行
逻辑上表现为三个执行实例并发执行
但实质物理上任然是串行执行
串行:一个处理完再一个
并发和并行:
并发:处理多个任务,不一定同时
例如:你正在吃饭,吃到一半电话响,去接电话,接完后继续吃饭
并行:同时处理多个任务
例如:边吃饭边打电话
3、多核CPU如何实现“多任务?
真正的并⾏执⾏多任务只能在多核CPU上实现,但是,由于任务数量远远多 于CPU的核⼼数量,所以,操作系统也会⾃动把很多任务轮流调度到每个核 ⼼上执⾏。
二、多进程编程
1、进程的创建
(1)程序和进程的区别
程序:编写完的代码,没有运行
进程:正在运⾏着的代码,需要运⾏的环境等
(2)进程的五状态模型:
创建(created)-----就绪(ready)-----运行(running)-----阻塞(waiting)-----结束(terminated)
(3)创建子进程
Python的os模块封装了常⻅的系统调⽤,其中就包括fork,可以在Python程 序中轻松创建⼦进程:
Python的os模块中的fork()函数,用来创建子进程,但是只能用在linux系统中。
fork()函数理解:
执⾏到os.fork()时,操作系统会创建⼀个新的进程复制⽗进程的所有信息到⼦进程中普通的函数调⽤,调⽤⼀次,返回⼀次,但是fork()调⽤⼀次,返回两次⽗进程和⼦进程都会从fork()函数中得到⼀个返回值,⼦进程返回是0,⽽⽗进程中返回⼦进程的 id号
多进程中,每个进程中所有数据(包括全局变量)都各有拥有⼀份,互不影响。
注:(windows 平台下无法使用 os.fork ,IDE 虽然不会报错.但是程序执行起来之后确实无法使用)
"""
多进程中,每个进程中所有数据(包括全局变量)都各拥有⼀份,互不影响
"""
import os
import time
# 定义一个全局变量money
money = 100
print("当前进程的pid:", os.getpid())
print("当前进程的父进程pid:", os.getppid())
# time.sleep(115)
p = os.fork() #windows 平台下无法使用 os.fork ,IDE 虽然不会报错.但是程序执行起来之后确实无法使用
# 子进程返回的是0
if p == 0:
money = 200
print("子进程返回的信息, money=%d" % (money))
# 父进程返回的是子进程的pid
else:
print("创建子进程%s, 父进程是%d" % (p, os.getppid()))
print(money)
2、多进程编程
Windows没有fork调⽤,由于Python是跨平台的, multiprocessing模块就是跨平台版本 的多进程模块。
multiprocessing模块提供了⼀个Process类来代表⼀个进程对象。
Process([group [, target [, name [, args [, kwargs]]]]])
target:表示这个进程实例所调⽤对象;就是进程要执行的内容
args:表示调⽤对象的位置参数元组;
kwargs:表示调⽤对象的关键字参数字典;
name:为当前进程实例的别名;
group:⼤多数情况下⽤不到;
Process类常⽤⽅法:
is_alive() : 判断进程实例是否还在执⾏;
join([timeout]) : 是否等待进程实例执⾏结束,或等待多少秒;
start() : 启动进程实例(创建⼦进程);
run() : 如果没有给定target参数,对这个对象调⽤start()⽅法时,就将执 ⾏对象中的run()⽅法;
terminate() : 不管任务是否完成,⽴即终⽌;
Process类常⽤属性:
name:当前进程实例别名,默认Process-N,N为从1开始计数;
pid:当前进程实例的PID值
多进程编程方法一:实例化对象
from multiprocessing import Process
import time
def task1():
print("正在听音乐")
time.sleep(1)
def task2():
print("正在编程......")
time.sleep(0.5)
def no_multi():
task1()
task2()
def use_multi():
p1 = Process(target=task1)
p2 = Process(target=task2)
p1.start()
p2.start()
p1.join()
p2.join()
# p.join() 阻塞当前进程, 当p1.start()之后, p1就提示主进程, 需要等待p1进程执行结束才能向下执行, 那么主进程就乖乖等着, 自然不会执行p2.start()
# [process.join() for process in processes]
if __name__ == '__main__':
# 主进程
start_time= time.time()
# no_multi()
use_multi()
end_time = time.time()
print(end_time-start_time)
运行结果:
正在听音乐
正在编程......
1.1266577243804932
注意:对于代码中join() 方法的使用:
- 在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源。
- 如果主进程的任务在执行到某一个阶段时,需要等待子进程完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程是否运行完毕。
- 在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用。
join()的作用:在进程中可以阻塞主进程的执行, 直到等待子线程全部完成之后, 才继续运行主线程后面的代码。
def use_multi():
p1 = Process(target=task1)
p2 = Process(target=task2)
p1.start()
#p1.join() # 如果写在这里,就是执行一个等待一个,
# 等p1进程结束后,才能向下执行,也就是相当于一条一条执行,没有用到多进程
p2.start()
p1.join() # 等待p1进程执行结束
p2.join() # 等待p2进程执行结束
# 其实此时的p1,p2是在同时进行,他俩都start开启了,等待他们结束的时间是最长进程的那个时间。
# 就不是在一个一个等了,一起等。
多进程编程方法二:创建子类(继承的式)
说白了即就是重写run() 方法,run()方法里就是要执行的任务
"""
创建子类, 继承的方式
"""
from multiprocessing import Process
import time
class MyProcess(Process):
"""
创建自己的进程, 父类是Process
"""
def __init__(self, music_name):
super(MyProcess, self).__init__()
se