# 1.先验证GIL的存在 from threading import Thread, Lock import time money = 100 def task(): global money money -= 1 for i in range(100): # 相当于创建了一百个线程 t = Thread(target=task) t.start() print(money) # 0 没有加锁相当于100个线程同时运行了 # 2.在验证不同数据加上不同的锁 from threading import Thread, Lock import time money = 100 mutex = Lock() # 加上互斥锁 def task(): global money mutex.acquire() # 获取一把锁 让他们去抢锁 tmp = money time.sleep(0.1) money = tmp - 1 mutex.release() # 释放一把锁 """ 抢锁放锁也有简便的写法(通过with上下文管理) with mutex: pass """ t_list = [] # 设置成一个空列表 用于线程结束后接收数据 然后在循环取值 # 目的是为了证明线程是否全部结束 for i in range(100): # 创建一百个线程 t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() # 为了确保结果正确 应该等待所有的线程运行完毕后再打印money print(money) # 99 """ GIL是一个纯理论知识 在实际工作中根本无需考虑它的存在 GIL作用面很窄 仅限于解释器级别 可以认为是解释器本身自带的一种锁 所以只能在解释器上使用 后期我们要想保证数据的安全应该自定义互斥锁(使用别人封装好的工具)"""
""" 两个大前提 1.CPU的个数 单个 多个 2.任务的类型 IO密集型 计算密集型 """ # 单个CPU 多个IO密集型任务 多进程:浪费资源 无法利用多个CPU 多线程:节省资源 切换+保存状态 多个计算密集型任务 多进程:耗时更长 创建进程的消耗+切换消耗 多线程:耗时较短 切换消耗 # 多个CPU 多个IO密集型任务 多进程:浪费资源 多个cpu无用武之地 多线程:节省资源 切换+保存状态 多个计算密集型任务 多进程:利用多核 速度更快 多线程:速度较慢 结论:多进程和多线程都有具体的应用场景 尤其是多线程并不是没有用!!! """代码验证""" from threading import Thread from multiprocessing import Process import os import time def work(): res = 1 for i in range(1,1000): res *= i if __name__ == '__main__': # print(os.cpu_count()) # 4 查看当前计算机cpu的个数 进程: start_time = time.time() p_list = [] for i in range(4): p = Process(target=work) p.start() p_list.append(p) for p in p_list: p.join() print('总耗时:%s' %(time.time()-start_time)) 线程: t_lsit = [] for i in range(4): t = Thread(target=work) t.start() t_list.append(t) for t in t_list: t.join() print('总耗时:%s' %(time.time()-start_time)) """ 以上总结: 计算密集型 多进程:0.08273792266845703 多线程:0.28725099563598633 两者差了一个数量级(越多差距越大) 结论:多进程比多线程更好""" 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.007348060607910156 多进程 总耗时:0.1564030647277832 两者差了两个数量级 结论 多线程更好"""
3、死锁现象
# 锁就算掌握了如何抢 如何放 也会产生死锁现象 from threading import Thread, Lock import time # 产生两把(复习 面向对象和单例模式):每天都可以写写单例啊 算法啊... mutexA = Lock() mutexB = Lock() class MyThread(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print(f'{self.name}抢到了A锁') mutexB.acquire() print(f'{self.name}抢到了B锁') mutexB.release() mutexA.release() def f2(self): mutexB.acquire() print(f'{self.name}抢到了B锁') time.sleep(2) mutexA.acquire() print(f'{self.name}抢到了A锁') mutexA.release() mutexB.release() for i in range(20): t = MyThread() t.start() """锁不能轻易使用并且以后我们也不会在自己去处理锁都是用别人封装的工具"""
信号量在不同的知识体系中 展示出来的功能是不一样的 eg: 在并发编程中信号量意思是多把互斥锁 在django框架中信号量意思是达到某个条件自动触发特定功能 """ 如果将自定义互斥锁比喻成是单个厕所(一个坑位) 那么信号量相当于是公共厕所(多个坑位) """ from threading import Thread, Semaphore import time import random sp = Semaphore(5) # 创建一个有五个坑位(带门的)的公共厕所 def task(name): sp.acquire() # 抢锁 print('%s正在蹲坑' % name) time.sleep(random.randint(1, 5)) sp.release() # 放锁 for i in range(1, 31): t = Thread(target=task, args=('伞兵%s号' % i, )) t.start() # 只要是跟锁相关的几乎都不会让我们自己去写 后期还是用模块
""" 子线程的运行可以由其他子线程决定!!! """ 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简便)
补充: 服务端必备的三要素 1.24小时不间断提供服务 2.固定的ip地址和端口port 3.支持高并发 回顾: TCP服务端实现并发 多进程:来一个客户端就开一个进程(临时工) 多线程:来一个客户端就开一个线程(临时工) 问题: 计算机硬件是有物理极限 我们不可能无限制的创建进程和线程 措施: 池:保证计算机硬件安全的情况下提升程序的运行效率 进程池:提前创建好固定数量的进程 后续反复使用这些进程(合同工) 线程池:提前创建好固定数量的线程 后续反复使用这些线程(合同工) 如果任务超出了池子里面的最大进程或线程数 则原地等待 强调: 进程池和线程池其实降低了程序的运行效率 但是保证了硬件的安全!! # 代码演示(掌握) from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor # 必写代码 # 线程池 ProcessPoolExecutor(5) # 线程池线程数默认为是CPU个数的五倍 也可以自定义 """上面的代码执行之后就会立刻创建五个等待工作的线程 不应该自己主动等待结果 应该让异步提交自动提醒>>>>:从而引出了“异步回调机制”""" pool.submit(task,i).add_done_callback(func) # 必写代码 """add_done_callback只要任务有结果了 就会自动调用括号内的函数处理""" # 进程池 pool = ProcessPoolExecutor(5) # 进程池进程数默认是CPU个数 也可以自定义 '''上面的代码执行之后就会立刻创建五个等待工作的进程''' pool.submit(task, i).add_done_callback(func)
""" 进程:资源单位 线程:执行单位 协程:单线程下实现并发 并发的概念:切换+保存状态 首先需要强调的是协程完全是程序员自己意淫出来的名词!!! 对于操作系统而言之认识进程和线程 协程就是自己通过代码来检测程序的IO操作并自己处理 让CPU感觉不到IO的存在从而最大幅度的占用CPU 类似于一个人同时干接待和服务客人的活 在接待与服务之间来回切换!!! """ # 基本使用 # 保存的功能 我们其实接触过 yield 但是无法做到检测IO切换 from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作 from gevent import spawn import time def play(name): print('%s play 1' % name) time.sleep(5) print('%s play 2' % name) def eat(name): print('%s eat 1' % name) time.sleep(3) print('%s eat 2' % name) start_time = time.time() g1 = spawn(play, 'jason') g2 = spawn(eat, 'jason') g1.join() # 等待检测任务执行完毕 g2.join() # 等待检测任务执行完毕 print('总耗时:', time.time() - start_time) # 正常串行肯定是8s+ # 5.00609827041626 代码控制切换
from gevent import monkey;monkey.patch_all() from gevent import spawn import socket def communication(sock): while True: data = sock.recv(1024) # IO操作 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) g1 = spawn(get_server) g1.join() """ 终极结论 python可以通过开设多进程 在多进程下开设多线程 在多线程使用协程 从而让程序执行的效率达到极致!!! 但是实际业务中很少需要如此之高的效率(一直占着CPU不放) 因为大部分程序都是IO密集型的 所以协程我们知道它的存在即可 几乎不会真正去自己编写 """