day11-进程和线程
1. 进程
进程:进行中的应用程序 属于一个程序的运行状态 属于CPU分配资源的最小单位
2. 线程
一个进程至少有一个线程 否则无法执行 属于CPU调度执行的最小单位
3. 进程和线程的执行机制
单核心场景下 进程以及线程 都是"随机"轮流交替执行的 CPU调度执行各个进程或者线程参考的条件因素非常多
CPU时间片轮 优先等级(进程或者线程) 调度策略 等等
CPU执行多个任务,每个任务执行的时间非常短暂,以毫秒为单位,所以宏观上看上去,像是同时在执行
微观的角度,随机轮流交替执行的
4. 并发和并行
并发:同时发生,轮流交替执行
并行:真正意义上的同时执行
5. 线程或者进程不是越多越好
要结合实际的硬件环境
6. 同步和异步
同步:多个线程有排队机制的(顺序通常也无法保证),效率比较低,但是可以保证数据安全
异步:各个线程各自执行,没有先后顺序,没有排队机制,效率高,但是数据不安全
7. 进程的使用
7.1 进程创建方式1
实现多进程方式1:使用multiprocessing.Process类 创建进程 观察两个进程轮流交替执行的过程
关于打印结果的问题: 1.多个进程是轮流交替执行的 先后顺序无法保证 2.多条输出语句并行打印 因为打印的时候文字和换行是分开打印的 所以会出现这种现象 3.程序结束在最后一行 却最先打印的问题 因为这条输出语句有main进程负责执行的 执行的内容相对较少 所以大概率先执行完 如果需要子进程执行完毕再执行主进程 则可以使用 join函数
"""
实现多进程方式1:使用multiprocessing.Process类
创建进程 观察两个进程轮流交替执行的过程
关于打印结果的问题:
1.多个进程是轮流交替执行的 先后顺序无法保证
2.多条输出语句并行打印 因为打印的时候文字和换行是分开打印的 所以会出现这种现象
3.程序结束在最后一行 却最先打印的问题
因为这条输出语句有main进程负责执行的 执行的内容相对较少 所以大概率先执行完
如果需要子进程执行完毕再执行主进程 则可以使用 join函数
"""
import multiprocessing as mp
import os
import time
# 编写函数 用于进程所执行的任务
def task():
for i in range(10):
print(f"进程名称:{mp.current_process().name},进程ID{os.getpid()}执行任务:{i}")
time.sleep(0.1)
if __name__ == '__main__':
p1 = mp.Process(target = task , name = "进程A")
p2 = mp.Process(target = task , name = "******进程B******")
p1.start() # 这里表示进程就绪
p2.start() # 这里表示进程就绪
p1.join() # 表示主进程等待当前进程运行完毕 再继续运行
p2.join() # 表示主进程等待当前进程运行完毕 再继续运行
print("程序结束")
7.2 进程创建方式2
实现多进程方式2:继承multiprocessing.Process类 init__函数 如果在本类中编写了__init 那么必须将父类的__init__重新调用 因为如果不调用 则表示覆盖了父类的实现 而父类中的实现默认会初始化很多信息 所以一旦覆盖就会报错了
重写run方法 run方法用于编写进程需要执行的内容
面试题:调用start和调用run的区别?调用start属于向CPU发出信息 表示当前进程准备就绪 可以被执行 等待CPU在合适的时机 开辟进程并且执行这个进程 直接调用run不会开辟进程 直接由主进程MainProcess类执行run函数
"""
实现多进程方式2:继承multiprocessing.Process类
__init__函数 如果在本类中编写了__init__ 那么必须将父类的__init__重新调用
因为如果不调用 则表示覆盖了父类的实现 而父类中的实现默认会初始化很多信息
所以一旦覆盖就会报错了
重写run方法 run方法用于编写进程需要执行的内容
面试题:调用start和调用run的区别?
调用start属于向CPU发出信息 表示当前进程准备就绪 可以被执行 等待CPU在合适的时机 开辟进程并且执行这个进程
直接调用run不会开辟进程 直接由主进程MainProcess类执行run函数
"""
import multiprocessing as mp
import time
import os
class MyProcess(mp.Process):
def __init__(self,name):
super().__init__() # 必须调用父类的__init__
self.name = name # 创建当前类对象 可以传入一个name参数 用于指定进程的名字
def run(self):
"""
用于记录进程要执行的内容
:return:
"""
for i in range(10):
name = mp.current_process().name
pid = os.getpid()
print(f"进程名称:{name},进程id{pid},执行任务{i}")
time.sleep(0.2)
if __name__ == "__main__":
p1 = MyProcess("******进程A******")
p2 = MyProcess("进程B")
p1.start()
p2.start()
p1.join()
p2.join()
# p1.run()
# p2.run()
print("程序结束")
7.3 进程创建方式3
创建进程方式3:进程池的方式 进程池可以自行管理我们创建的进程 提高进程的复用性
"""
创建进程方式3:进程池的方式
"""
from multiprocessing import Pool
import os
import time
def task(task_name):
print(f"进程池执行任务{task_name},进程id{os.getpid()}正在执行")
time.sleep(0.2)
print(f"进程池执行任务{task_name},进程id{os.getpid()}执行完毕")
if __name__ == '__main__':
pool = Pool(3) # 创建3个进程
for i in range(10):
pool.apply(func = task,args=(i,)) # 同步的方式执行
# pool.apply_async(func = task,args=(i,)) # 异步的方式执行
pool.close() #表示不再往池中添加任务了
pool.join()
print("程序结束")
7.4 进程不共享全局变量
进程之间默认是不共享全局变量 即每个进程独自访问一份全局变量 多个进程访问多份 互不影响
"""
进程之间默认是不共享全局变量 即每个进程独自访问一份全局变量
多个进程访问多份 互不影响
"""
import os
from multiprocessing import Process
import time
def func(build_list):
for i in range(10):
build_list.append(i)
print(f"进程id:{os.getpid()}",build_list)
time.sleep(0.1)
if __name__ == '__main__':
list1 = []
p1 = Process(target=func , name = "进程A" ,args=(list1,))
p2 = Process(target=func , name = "*****进程B*****" ,args=(list1,))
p1.start()
p2.start()
p1.join()
p2.join()
print("程序结束")
8. 线程
8.1 线程创建方式1
创建线程方式1:threading.Thread 删除线 说明这个API被弃用了 表示不推荐使用 通常是有更好的解决方案了 注意:已弃用并不是删除了 不能用了 因为还要兼顾一些老的系统中使用 只是提醒我们 不推荐使用了
"""
创建线程方式1:threading.Thread
删除线 说明这个API被弃用了 表示不推荐使用 通常是有更好的解决方案了
注意:已弃用并不是删除了 不能用了 因为还要兼顾一些老的系统中使用
只是提醒我们 不推荐使用了
"""
import threading as td
import time
def run():
for i in range(10):
print(f"线程名称{td.current_thread().name}打印数据:{i}")
time.sleep(0.1)
if __name__ == '__main__':
t1 = td.Thread(target=run,name="******线程A******")
t2 = td.Thread(target=run,name="线程B")
t1.start()
t2.start()
t1.join()
t2.join()
print("程序结束")
8.2 线程创建方式2
创建线程方式2:编写子类继承Thread父类
"""
创建线程方式2:编写子类继承Thread父类
"""
from threading import Thread
import time
class MyThread(Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
for i in range(10):
print(f"线程名称:{self.name}打印数据{i}")
time.sleep(0.1)
if __name__ == '__main__':
t1 = MyThread("*****线程A*****")
t2 = MyThread("线程B")
t1.start()
t2.start()
t1.join()
t2.join()
print("程序结束")
8.3 线程创建方式3
创建线程方式3:线程池的方式
"""
创建线程方式3:线程池的方式
"""
import time
from concurrent.futures import ThreadPoolExecutor
import threading as td
def task():
print(f"线程名称:{td.current_thread().name}开始执行任务")
time.sleep(1)
print(f"线程名称:{td.current_thread().name}开始任务完毕")
if __name__ == '__main__':
with ThreadPoolExecutor(max_workers=2) as pool:
for i in range(10):
pool.submit(task)
print("程序结束")
9. 线程安全问题
使用多线程模拟多人抢票 线程安全的问题 三个人抢10张票 不能超卖 不能重复卖出
互斥锁(同步锁) 可重入锁 不可重入锁 自旋锁 无锁
"""
使用多线程模拟多人抢票 线程安全的问题
三个人抢10张票 不能超卖 不能重复卖出
互斥锁(同步锁)
可重入锁 不可重入锁 自旋锁 无锁
"""
import threading as td
import time
tickets = 10 # 总票数 10张
lock = td.Lock() # 创建锁对象
def buy_ticket():
global tickets
while True:
time.sleep(1)
lock.acquire() # 上锁 从这里开始只允许一个线程访问
if tickets == 0:
lock.release()
break # 结束循环
tickets -= 1 # 票数-1
print(f"{td.current_thread().name}抢到了第{10 - tickets}张票,还剩余{tickets}张票")
lock.release() # 释放锁 其他线程可以再次从上方进入代码体执行
print("票卖完了")
if __name__ == "__main__":
t1 = td.Thread(target=buy_ticket,name="赵四")
t2 = td.Thread(target=buy_ticket,name="小宝")
t3 = td.Thread(target=buy_ticket,name="大拿")
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print("程序结束")
10. GIL
GIL全局解释器锁 是一个锁 同时只允许一个线程执行 所以Python中的多线程 不是真正意义上的多线程 为什么Python使用全局解释器锁 限制了硬件的能力 1.历史原因 Python诞生于90年代 硬件环境不支持多核 2.早期的C语言 作为Python的底层代码 本身线程是不安全的 Python为了解决线程安全这个问题 所以在加了GIL全局解释器锁(一把大锁) 3.虽然GIL限制了Python多线程的能力,但是Python能处理的任务大多数是不需要多线程并发执行的
Python擅长处理什么类型的任务?IO密集型的任务 A CPU密集的任务 需要做大量的计算 比如图形解析 复杂算法等等 B IO密集型任务 比如网络请求 文件读写 数据库操作 脚本开发 AI机器学习
"""
GIL全局解释器锁
是一个锁 同时只允许一个线程执行 所以Python中的多线程 不是真正意义上的多线程
为什么Python使用全局解释器锁 限制了硬件的能力
1.历史原因 Python诞生于90年代 硬件环境不支持多核
2.早期的C语言 作为Python的底层代码 本身线程是不安全的 Python为了解决线程安全这个问题
所以在加了GIL全局解释器锁(一把大锁)
3.虽然GIL限制了Python多线程的能力,但是Python能处理的任务大多数是不需要多线程并发执行的
Python擅长处理什么类型的任务?IO密集型的任务
A CPU密集的任务 需要做大量的计算 比如图形解析 复杂算法等等
B IO密集型任务 比如网络请求 文件读写 数据库操作 脚本开发 AI机器学习
"""
import threading
import multiprocessing
import time
def task():
count = 0
while count < 100000000:
count += 1
if __name__ == '__main__':
start = time.time()
t1 = threading.Thread(target=task)
t2 = threading.Thread(target=task)
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()
print(f"多线程耗时:{end - start :.2f}s")
start = time.time()
p1 = multiprocessing.Process(target=task) # 每个进程各自使用一个单独GIL
p2 = multiprocessing.Process(target=task) # 每个进程各自使用一个单独GIL
p1.start()
p2.start()
p1.join()
p2.join()
end = time.time()
print(f"多进程耗时:{end - start :.2f}s")