并发编程-4
并发编程
目录
- 验证GIL的存在
- 验证python多线程是否有用
- 死锁现象
- 信号量
- event事件
- 进程池和线程池
- 协程
GIL
-
验证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() # 等待所有的线程运行结束 查看money是多少 print(money) # 99
-
验证GIL的特点
GIL只会管解释器层面的安全,不会管应用程序种自己的数据
GIL不会影响程序层面的数据也不会保证它的修改是安全的要想保证得自己加锁
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() # 等待所有的线程运行结束 查看money是多少 print(money) # 0 如若想将money为0 ,因为有延迟的情况,故而GIL可视为不存在 需要自行添加锁,将线程由并发变成串行 先将线程运行,先抢GIL这锁,然后再此抢自己生成的锁,有IO操作则释放GIL锁,线程运行完之后释放自己所生成的锁,下一个线程抢这个锁
验证python多线程的作用
-
在单个cpu中的多进程多线程的区别
单个CPU IO密集型(代码有IO操作) 计算密集型(代码没有IO) 多进程 申请额外的空间 消耗更多的资源 申请额外的空间 消耗更多的资源
(总耗时+申请空间+拷贝代码+切换)多线程 消耗资源相对较少 通过多道技术 消耗资源相对较少 通过多道技术(总耗时+切换) 单个CPU中多线程有优势!!!
-
具体体现
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() 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)) # 获取总的耗时 def work(): time.sleep(2) # 模拟纯IO操作 if __name__ == '__main__': start_time = time.time() 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)) 多个CPU
-
-
多个CPU中多进程多线程的区别
多个CPU IO密集型 计算密集型 多进程 总耗时(单个进程的耗时+IO+申请空间+拷贝代码) 总耗时(单个进程的耗时) 多线程 总耗时(单个进程的耗时+IO) 总耗时(多个进程的综合) 优势 多线程有优势!!! 多进程完胜!!! -
具体体现
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.0149583816528320 多进程:0.6402878761291504 """
-
死锁
-
死锁的理解
死锁就类似于死循环,死循环是两个循环来回调用
而死锁则是在两把锁之间的线程/进程,先由一个线程抢锁然后执行操作,随后抢第二个锁,释放第一个锁,第二个线程抢第一个锁,此时,第一个第二个锁都在进程手中,无法抢夺。故为死锁
-
代码实现:
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()
信号量
-
信号量本质
信号量本质上也是互斥锁 只不过它是多把锁
-
信号量在不同的知识体系中 意思可能有区别
不同的体系 含义 并发编程中 信号量就是多把互斥锁 django中 信号量指的是达到某个条件自动触发(中间件) -
信号量的代码实现
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()
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()
进程池和线程池
-
进程池和线程池的简介
-
什么是池
降低程序的执行效率 保证计算机硬件的安全
-
进程池和线程池的理解
分类 意义 进程池 提前创建好固定个数的进程供程序使用 后续不会再创建 线程池 提前创建好固定个数的线程供程序使用 后续不会再创建 -
对于开设过多进程和线程的情况
我们在开设多进程或者多线程的时候 还需要考虑硬件的承受范围,过多的话会造成内存溢出受限于硬件水平
-
-
进程池和线程池实操
submit(函数名,实参1,实参2,...) 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)
协程
-
协程简介
协程就是让CPU误以为执行的代码中没有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)