12 认识进程与线程 (进阶)
阶段一:并发与并行的深入理解
并行一定是并发,但并发不一定是并行。
并行是相对的,并行是绝对的。
1、关于并行与并发的问题引入:
问题一: 计算机是如何执行程序指令的?
问题二: 计算机如何模拟出并行执行的效果?
问题三: 真正的并行需要依赖什么?
2、计算机执行指令示意图
2、轮询调度实现并发执行
并发:看上去一起执行,同时在发生
并行:真正一起执行,同时在进行
调度算法:
时间片轮转
优先级调度
3、并行需要的核心条件
并行真正的核心条件是有多个CPU
阶段二:多进程实现并行
1、多进程并行问题引入
问题一: 什么是进程?
问题二: 如何在Python中使用进程?
问题三: 多进程实现并行的必要条件是什么?
2、进程的概念
计算机程序是存储在磁盘上的可执行二进制(或其他类型)文件。
只有把它们加载到内存中,并被操作系统调用它们才会拥有其自己的生命周期。
进程则是表示的一个正在执行的程序。
每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据
操作系统负责其上所有进程的执行。
操作系统会为这些进程合理地分配执行时间。
3、在Python中直接执行耗时函数
import time print('main-task start:', time.asctime(time.localtime(time.time()))) def func(): print('sub-task start:', time.asctime(time.localtime(time.time()))) time.sleep(5) print('sub-task end:', time.asctime(time.localtime(time.time()))) func() time.sleep(5) print('main-task end:', time.asctime(time.localtime(time.time())))
4、在Python中使用进程来分担耗时任务
import time import multiprocessing def func(n): for i in range(n): for a in range(n): for b in range(n): print(b) start_time = time.time() p = multiprocessing.Process(target=func, args=(50, )) # 实例化,创建一个进程 # 参数如何传? args=(50, ) kwargs={'n': 50} p.start() # 开启进程 p.join() # 主进程等待子进程结束 func(50) # func(50) end_time = time.time() print('运行了%ds!' % (end_time - start_time))
5、多进程并行的必要条件
总进程数量不多于CPU核心数量!
因此,现在运行的程序都是轮询调度产生的并行假象。但是在Python层面的确获得了并行!
阶段三:多线程实现并发
1、多线程并发问题引入
问题一: 什么是线程?
问题二: 如何在Python中使用线程?
问题三: 为什么多线程不是并行?
2、线程的概念
线程被称作轻量级进程。
与进程类似,不过它们是在同一个进程下执行的。并且它们会共享相同的上下文。
当其他线程运行时,它可以被抢占(中断)和临时挂起(也成为睡眠)— 让步
线程的轮询调度机制类似于进程的轮询调度。只不过这个调度不是由操作系统来负责,而是由Python解释器来负责。
3、在Python中使用线程来避开阻塞任务
import time import multiprocessing import threading print('---outer--start---:', time.asctime(time.localtime(time.time()))) def func(): print('---inner--start---:', time.asctime(time.localtime(time.time()))) time.sleep(5) print('---inner--end---:', time.asctime(time.localtime(time.time()))) """ 在进程里可以模拟耗时任务,但是在线程里只能模拟阻塞任务,不能模拟耗时任务。因为多线程只有一个核心进程。 """ p = multiprocessing.Process(target=func) # 创建子进程 t = threading.Thread(target=func) # 创建子线程 t.start() # 开启子线程 time.sleep(5) print('---outer--end---:', time.asctime(time.localtime(time.time())))
CPU在任意一个进程里,任意时刻,只能执行一个线程
对进程的轮询是操作系统负责调度
对线程的轮询是Python解释器负责调度
4、GIL锁 全局解释器锁
Python在设计的时候,还没有多核处理器的概念。
因此,为了设计方便与线程安全,直接设计了一个锁。
这个锁要求,任何进程中,一次只能有一个线程在执行。
因此,并不能为多个线程分配多个CPU。
所以Python中的线程只能实现并发,
而不能实现真正的并行。
但是Python3中的GIL锁有一个很棒的设计,
在遇到阻塞(不是耗时)的时候,会自动切换线程。
5、GIL锁带给我们的新认知
遇到阻塞就自动切换。因此我们可以利用这种机制来有效的避开阻塞~充分利用CPU
阶段四:使用多进程与多线程来实现并发服务器
关键点一: 多进程是并行执行,
相当于分别独立得处理各个请求。
关键点二: 多线程,虽然不能并行运行,
但是可以通过避开阻塞切换线程
来实现并发的效果,并且不浪费CUP
from socket import * from multiprocessing import Process # 进程 from threading import Thread # 线程 # 创建套接字 server = socket() server.bind(('', 9999)) server.listen(1000) # 定义函数 def func(conn): while True: recv_data = conn.recv(1024) if recv_data: print(recv_data) conn.send(recv_data) else: conn.close() break while True: # 循环去监听 conn, addr = server.accept() # 每生成一个对等连接套接字,我就生成一个进程、线程,并且我让这个进程、线程去服务这个连接过来的客户端 # p = Process(target=func, args=(conn, )) # 生成一个进程 # p.start() # 启动进程 t = Thread(target=func, args=(conn, )) # 生成一个线程 t.start() # 启动线程