1、多任务介绍
多任务就是在同一时间同时运行多个任务,比如在跳舞的时候唱歌,在看书的时候听歌,电脑在运行微信时,也在运行QQ,word等软件,这些都是多任务的应用,多任务可以提高执行效率,充分利用CPU资源,让程序具备处理多个任务的能力。
执行方式:
- 并行
- 并发
并发:在一段时间内,交替执行任务
并发针对单核cpu,存在竞争关系,都在争夺cpu资源,严格来说并不是真正意义的多任务执行,只是间隔时间很短,我们肉眼不能区分,看上去像同时在执行一样
例如:
只有1位辅导老师,学生都想让老师为自己辅导,每位学生之间存在竞争关系,老师每次只能为1位学生辅导,因此对老师来说就是并发执行辅导学生的任务,辅导完甲同学,再去辅导乙同学,接着再去辅导丙同学。
并行:同一时间,同时执行多个任务
针对多核CPU,操作系统会给每个内核分配一个任务,属于真正的一起执行任务,多核cpu是并行执行多任务,始终有多个任务同时执行。
注意:
- 只有多核CPU才能真正意义上让多个软件一起运行
- 任务数小于cpu核数,并行方式运行
- 任务数大于cpu核数,并发方式运行
实现多任务的方式:
- 进程
- 线程
- 协程
2、进程
2.1进程介绍
进程顾名思义“进行当中的程序”,因此进程必须是正在运行当中,如果没有运行就不能叫做进程,只能叫程序。只要程序开始运行,即进程启动,操作系统就会分配内存资源,因此进程是操作系统进行资源分配的基本单位。
进程只负责向操作系统索要内存资源,而实际执行任务的是线程,启动1个程序,至少有1个进程,1个进程默认有1个线程,线程依附于进程,没有进程就没有线程
例如:
进程可以任务就是一个公司,线程是员工,真正干活的是员工,公司只为员工提供办公场地,没有公司也就没有员工
2.2多进程的使用
进程之间不共享全局变量,创建子进程会对主进程资源进行拷贝,子进程是主进程的一个副本,就像是一对双胞胎,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已
process中有2中传参方式:
- args:按照元组方式传参数
注意:必须和参数的顺序相对应 - kwargs:按照字典方式传参数
-注意:字典的key要和参数保持想对应
# 导入进程包
import multiprocessing
import time
import os
def dance(i):
# 获取当前进程的编号
print("当前dance进程的编号是:",os.getpid())
# 获取当前进程的父进程id
print("当前dance进程的父进程编号是:",os.getppid())
# 获取dance进程
print("dance:",multiprocessing.current_process())
for i in range(i):
print("我在跳舞中")
time.sleep(0.2)
def sing(count):
# 获取当前进程的编号
print("当前sing进程的编号是:",os.getpid())
# 获取当前进程的父进程id
print("当前sing进程的父进程编号是:",os.getppid())
# 获取sing进程
print("sing:",multiprocessing.current_process())
for i in range(count):
print("我在唱歌中")
time.sleep(0.2)
if __name__ == '__main__':
# 获取当前主进程的编号:
print("当前main进程的编号:",os.getpid())
# 获取主进程
print("main:",multiprocessing.current_process())
# target:执行的目标任务名,name:进程名字
dance_process = multiprocessing.Process(target=dance,name="myprocess1",args=(2,))
sing_process = multiprocessing.Process(target=sing,name="myprocess2",kwargs={"count":2})
# 启动子进程实例(创建子进程)
dance_process.start()
sing_process.start()
运行结果:
当前main进程的编号: 17816
main: <_MainProcess(MainProcess, started)>
当前dance进程的编号是: 3404
当前sing进程的编号是: 19308
当前dance进程的父进程编号是: 17816
dance: <Process(myprocess1, started)>
我在跳舞中
当前sing进程的父进程编号是: 17816
sing: <Process(myprocess2, started)>
我在唱歌中
我在跳舞中
我在唱歌中
Process finished with exit code 0
3、线程和互斥锁
明白了进程,再说线程就简单很多了,线程就是真正做事的人,每个线程需要cpu调度才能执行任务,因此说,线程是cpu调度的基本单位,每个进程至少有一个线程,这个线程就是我们经常说的主线程
线程之间可以进行全局变量共享,为了保证数据准确,引入互斥锁概念
互斥锁:对共享数据进行锁定,使同一时间只能有1个线程使用,多个线程去抢这把锁,谁抢到就是谁使用,其他未抢到锁的线程,处于继续等待状态,待锁释放后继续重新抢
# 导入进程包
import threading
import time
# 创建锁
mutex = threading.Lock()
def study():
# 上锁
mutex.acquire()
# 获取当前线程
print("当前的study线程是:",threading.current_thread())
for i in range(3):
print("学习中。。。")
time.sleep(0.2)
# 释放锁
mutex.release()
def play():
# 上锁
mutex.acquire()
# 获取当前线程
print("当前的play线程是:",threading.current_thread())
for i in range(3):
print("玩耍。。。")
time.sleep(0.2)
# 释放锁
mutex.release()
if __name__ == '__main__':
study_thread = threading.Thread(target=study)
play_thread = threading.Thread(target=play)
# 启动线程
study_thread.start()
play_thread.start()
运行结果:
当前的study线程是: <Thread(Thread-1, started 5136)>
学习中。。。
当前的play线程是: <Thread(Thread-2, started 4544)>
玩耍。。。
玩耍。。。
学习中。。。
玩耍。。。
学习中。。。
Process finished with exit code 0
注意:
上锁之后应在适当的地方释放锁,不然容易造成死锁,导致程序卡死,崩溃,如下面这段代码:
import threading
import time
# 创建互斥锁
lock = threading.Lock()
# 根据下标去取值, 保证同一时刻只能有一个线程去取值
def value(index):
# 上锁
lock.acquire()
print(threading.current_thread())
mylist = [3,4,12]
# 判断下标释放越界
if index >= len(mylist):
print("下标越界:", index)
# 未释放锁,直接返回,导致线程卡死
return
value = mylist[index]
print(value)
time.sleep(0.2)
# 释放锁
lock.release()
if __name__ == '__main__':
# 模拟大量线程去执行取值操作
for i in range(30):
index_thread = threading.Thread(target=value, args=(i,))
index_thread.start()
执行结果:
卡死,无法正常退出
Thread(Thread-1, started 14876)>
3
<Thread(Thread-2, started 13652)>
4
<Thread(Thread-3, started 16336)>
12
<Thread(Thread-4, started 16512)>
下标越界: 3