【8.11】 并发编程-完结
今日内容概要
- 验证GIL的存在
- 验证python多线程是否有用
- 死锁现象
- 信号量
- event事件
- 进程池与线程池(重要)
- 协程
今日内容详细
验证GIL的存在
from threading import Thread
money = 100
def task():
global money
money -= 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t) #把运行的线程添加进去 [线程1 线程2 线程3 ... 线程100]
for t in t_list:
t.join()
# 等待所有的线程运行结束 查看结果
print(money) # 0
# 结果:
经验证确实有GIL存在 所以结果为0
验证GIL的特点
from threading import Thread
import time
money = 100
def task():
global money
tmp = money
time.sleep(0.1)
money =tmp - 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t) # 把运行的线程添加进去 [线程1 线程2 线程3 ... 线程100]
for t in t_list:
t.join()
# 等待所有的线程运行结束 查看结果
print(money) # 99
# 原理:
在程序进入IO时 CPU会闲置 线程会自动去匹配CUP 每个线程都会进入IO 所有每个线程都会先拿到100 然后IO结束 按照代码程序去减一
"""
GIL不会影响程序层面的数据 同时也不会保证它的修改是安全的 想要得到保证 需要自己去加锁控制代码
"""
# 处理措施:在依然进入IO的情况下 让其结果还等于0 解锁控制
from threading import Thread,Lock
import time
money = 100
mutex = Lock()
def task():
mutex.acquire()
global money
tmp = money
time.sleep(0.1)
money =tmp - 1
mutex.release()
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t) # 把运行的线程添加进去 [线程1 线程2 线程3 ... 线程100]
for t in t_list:
t.join()
# 等待所有的线程运行结束 查看结果
print(money) # 0
#原理:
在加锁之后 线程在进入程序后 先拿到锁后 进入IO 然后GIL会闲置 被其他线程占用 但是因为之前的线程没有解锁 所以拿到GIL没有用 必须等上一个线程释放锁之后 才能用 依次往返 每一个线程结果后 money都会少一 100个线程 代码走完 结果为0
验证python多线程是否有用
需要分情况
情况1
单个CPU
多个CPU
情况2
IO密集型(代码有IO操作)
计算密集型(代码没有IO)
1.单个CPU
IO密集型
多进程
申请额外的空间 消耗更多的资源
多线程
消耗资源相对较少 通过多道技术
ps:多线程有优势!!!
计算密集型
多进程
申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换)
多线程
消耗资源相对较少 通过多道技术(总耗时+切换)
ps:多线程有优势!!!
2.多个CPU
IO密集型
多进程
总耗时(单个进程的耗时+IO+申请空间+拷贝代码)
多线程
总耗时(单个进程的耗时+IO)
ps:多线程有优势!!!
计算密集型
多进程
总耗时(单个进程的耗时)
多线程
总耗时(多个进程的综合)
ps:多进程完胜!!!
from threading import Thread
from multiprocessing import Process
import os
import time
def work():
# 计算密集型
res = 1
for i in range(1, 100000):
res *= i
if __name__ == '__main__':
# print(os.cpu_count()) # 12 查看当前计算机CPU个数
start_time = time.time()
# p_list = []
# for i in range(12): # 一次性创建12个进程
# p = Process(target=work)
# p.start()
# p_list.append(p)
# for p in p_list: # 确保所有的进程全部运行完毕
# p.join()
t_list = []
for i in range(12):
t = Thread(target=work)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print('总耗时:%s' % (time.time() - start_time)) # 获取总的耗时
"""
计算密集型:【进程比较快】
多进程:5.665567398071289
多线程:30.233906745910645
"""
def work():
time.sleep(2) # 模拟纯IO操作
if __name__ == '__main__':
start_time = time.time()
# t_list = []
# for i in range(100):
# t = Thread(target=work)
# t.start()
# for t in t_list:
# t.join()
p_list = []
for i in range(100):
p = Process(target=work)
p.start()
for p in p_list:
p.join()
print('总耗时:%s' % (time.time() - start_time))
"""
IO密集型:【线程比较快】
多进程:0.6402878761291504
多线程:0.0149583816528320
"""
死锁现象
from threading import Thread, Lock
import time
mutexA = Lock() # 类名加括号每执行一次就会产生一个新的对象
mutexB = Lock() # 类名加括号每执行一次就会产生一个新的对象
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
print(f'{self.name}释放了B锁')
mutexA.release()
print(f'{self.name}释放了A锁')
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(1)
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
print(f'{self.name}释放了A锁')
mutexB.release()
print(f'{self.name}释放了B锁')
for i in range(10):
t = MyThread()
t.start()
# 过程:(死锁现象)
代码启动时 线程一线先执行func1 正常结束后 执行func2 在抢到B锁之后 程序睡了一秒进入IO 线程二这时进入func1抢到A锁 当要抢B锁的时候 在线程一手里 线程一睡醒后 正常程序要走到抢A锁的时候 在线程二手里 这样也就锁住了 形成了死锁现象
信号量
- 信号量本质也是互斥锁 只不过它多了把锁
- 强调:
- 信号量在不同的知识体系中 意思可能有区别
- 在并发编程中 信号量就是把互斥锁
- 在Django中 信号量指的是达到某个条件自动触发的(中间件)
- 信号量在不同的知识体系中 意思可能有区别
"""
我们之前使用Lock产生的是单把锁
类似于单间厕所
信号量相当于一次性创建多间厕所
类似于公共厕所
"""
from threading import Thread, Lock, Semaphore
import time
import random
sp = Semaphore(5) # 一次性产生五把锁
class MyThread(Thread):
def run(self):
sp.acquire()
print(self.name)
time.sleep(random.randint(1, 3))
sp.release()
for i in range(20):
t = MyThread()
t.start()
# 结果:
正常情况下 一个进程中多个线程(上述是20) 打印情况通常是一个接着一个打印 但是加入信号量(上述设定为5把锁) 一次性就会打印5个线程
event事件
- 子进程与子线程之间可以彼此等待彼此
- 子A运行到某一个代码位置后发信号告诉子B开始运行
from threading import Thread, Event
import time
event = Event() # 类似于造了一个红绿灯
def light():
print('红灯亮着的 所有人都不能动')
time.sleep(3)
print('绿灯亮了 油门踩到底 给我冲!!!')
event.set()
def car(name):
print('%s正在等红灯' % name)
event.wait()
print('%s加油门 飙车了' % name)
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car, args=('熊猫PRO%s' % i,))
t.start()
# 过程:
event.set() 结束
event.wait() 等待
配合使用 wait 在等待 set执行 一旦执行 wait就会触发
进程池和线程池
-
池:
- 降低程序的执行效率 保证计算机硬件的安全
-
进程池:
- 提前创建好固定个数的进程供程序使用 后续不会再创建
-
线程池:
- 提前创建好固定个数的线程供程序使用 后续不会再创建
-
多线程 多进程
- 在实际应用中是不可以无限制的开进程和线程的 容易造成内存溢出 受限于硬件水平
- 我们在开设多进程或者多线程的时候 还需要考虑硬件的承受范围
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from threading import current_thread
import os
import time
# pool = ThreadPoolExecutor(5) # 固定产生五个线程
pool = ProcessPoolExecutor(5) # 固定产生五个线程
def task(n):
# print(current_thread().name)
print(os.getpid())
# print(n)
time.sleep(1)
return '返回的结果'
def func(*args, **kwargs):
print('func', args, kwargs)
print(args[0].result())
if __name__ == '__main__':
for i in range(20):
# res = pool.submit(task,123) # 朝池子中提交任务(异步)
# print(res.result()) # 同步
# pool.submit(task, 123).add_done_callback(func)
"""异步回调:异步任务执行完成后有结果就会自动触发该机制"""
pool.submit(task, 123).add_done_callback(func)
协程
- 进程:资源单位
- 线程:执行单位
- 协程:单线程下实现并发(效率极高)
- 在代码层面欺骗CUP 让CUP觉得我们的代码里面没有IO操作
- 实际上IO操作被我们自己写的代码检测 一旦有IO 立刻上代码执行别的(该技术完全是程序员自己弄出来的 名字也是程序员自己起的)
- 核心:自己写代码完成 切换+ 保存状态
import time
from gevent import monkey;
monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn
def func1():
print('func1 running')
time.sleep(3)
print('func1 over')
def func2():
print('func2 running')
time.sleep(5)
print('func2 over')
if __name__ == '__main__':
start_time = time.time()
# func1()
# func2()
s1 = spawn(func1) # 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
s2 = spawn(func2)
s1.join()
s2.join()
print(time.time() - start_time) # 8.01237154006958 协程 5.015487432479858
协程实现TCP服务端并发
import socket
from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn
def communication(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, addr = server.accept() # IO操作
spawn(communication, sock)
s1 = spawn(get_server)
s1.join()
总结
- 如何不断的提升程序的运行效率:
- 多进程下多线程 多线程下开协程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)