7.并发编程
7.并发编程
11.1 操作系统发展史
参考博客即可:https://www.cnblogs.com/Dominic-Ji/articles/10929381.html
11.1.1 多道技术
单核实现并发的效果
11.1.2 必备知识点
并发
看起来像同时运行的就可以称之为并发
并行
真正意义上的同时执行
ps:
- 并行肯定算并发
- 单核的计算机肯定不能实现并行,但是可以实现并发!!!
补充:我们直接假设单核就是一个核,干活的就一个人,不要考虑 cpu 里面的内核数
11.1.3 多道技术重点知识
空间上的服用与时间上的服用
空间上的复用
多个程序公用一套计算机硬件
时间上的复用
例子:洗衣服 30s,做饭 50s,烧水 30s
单道需要 110s,多道只需要任务做长的那一个 切换节省时间
例子:边吃饭边玩游戏 保存状态
切换+保存状态
"""
切换(CPU)分为两种情况
1.当一个程序遇到IO操作的时候,操作系统会剥夺该程序的CPU执行权限
作用:提高了CPU的利用率 并且也不影响程序的执行效率
2.当一个程序长时间占用CPU的时候,操作吸引也会剥夺该程序的CPU执行权限
弊端:降低了程序的执行效率(原本时间+切换时间)
"""11.2 进程理论
11.2.1 必备知识点
程序与进程的区别
"""
程序就是一堆躺在硬盘上的代码,是“死”的
进程则表示程序正在执行的过程,是“活”的
"""11.2.2 进程调度
先来先服务调度算法
"""对长作业有利,对短作业无益"""短作业优先调度算法
"""对短作业有利,多长作业无益"""时间片轮转法+多级反馈队列
参考图解
11.2.3 进程运行的三状态图
参考图解了解即可
11.2.4 两对重要概念
同步和异步
"""描述的是任务的提交方式""" 同步:任务提交之后,原地等待任务的返回结果,等待的过程中不做任何事(干等) 程序层面上表现出来的感觉就是卡住了 异步:任务提交之后,不原地等待任务的返回结果,直接去做其他事情 我提交的任务结果如何获取? 任务的返回结果会有一个异步回调机制自动处理阻塞非阻塞
"""描述的程序的运行状态""" 阻塞:阻塞态 非阻塞:就绪态、运行态 理想状态:我们应该让我们的写的代码永远处于就绪态和运行态之间切换
上述概念的组合:最高效的一种组合就是异步非阻塞
11.3 开启进程的两种方式
定心丸:代码开启进程和线程的方式,代码书写基本是一样的,你学会了如何开启进程就学会了如何开启线程
from multiprocessing import Process
import time
def task(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
## 在window系统中,创建进程一定在main下面下
## 因为window下创建进程类似于模块带入方式
if __name__ == '__main__':
# 1 创建一个对象
p = Process(target=task, args=('jason',))
# 容器类型哪怕里面只有1个元素 建议要用逗号隔开
# 2 开启进程
p.start() # 告诉操作系统帮你创建一个进程 异步
print('主')
# 第二种方式 类的继承
from multiprocessing import Process
import time
class MyProcess(Process):
def run(self):
print('hello bf girl')
time.sleep(1)
print('get out!')
if __name__ == '__main__':
p = MyProcess()
p.start()
print('主')总结
"""
创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去
一个进程对应在内存中就是一块独立的内存空间
多个进程对应在内存中就是多块独立的内存空间
进程与进程之间数据默认情况下是无法直接交互,如果想交互可以借助于第三方工具、模块
"""11.4 join 方法
join 是让主进程等待子进程代码运行结束之后,再继续运行。不影响其他子进程的执行
from multiprocessing import Process
import time
def task(name, n):
print('%s is running'%name)
time.sleep(n)
print('%s is over'%name)
if __name__ == '__main__':
# p1 = Process(target=task, args=('jason', 1))
# p2 = Process(target=task, args=('egon', 2))
# p3 = Process(target=task, args=('tank', 3))
# start_time = time.time()
# p1.start()
# p2.start()
# p3.start() # 仅仅是告诉操作系统要创建进程
# # time.sleep(50000000000000000000)
# # p.join() # 主进程等待子进程p运行结束之后再继续往后执行
# p1.join()
# p2.join()
# p3.join()
start_time = time.time()
p_list = []
for i in range(1, 4):
p = Process(target=task, args=('子进程%s'%i, i))
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('主', time.time() - start_time)11.5 进程之间数据相互隔离
from multiprocessing import Process
money = 100
def task():
global money # 局部修改全局
money = 666 ## 其实也修改了money的值,但是改的只是自己进程里的,外面的没改
print('子',money) ## 666
if __name__ == '__main__':
p = Process(target=task)
p.start()
p.join()
print(money) ## 10011.6 人工智能相关网站
https://www.xfyun.cn/?ch=bd05&b_scene_zt=1
http://ai.baidu.com/creation/main/demo
作为一名 python 程序员当你遇到一个功能的时候,第一时间你可以考虑是否有对应的模块已经帮你实现了该功能
11.7 进程对象及其他方法
"""
一台计算机上面运行着很多进程,那么计算机是如何区分并管理这些进程服务端的呢?
计算机会给每一个运行的进程分配一个PID号
如何查看
windows电脑
进入cmd输入tasklist即可查看
tasklist |findstr PID查看具体的进程
mac电脑
进入终端之后输入ps aux
ps aux|grep PID查看具体的进程
"""
from multiprocessing import Process, current_process
current_process().pid # 查看当前进程的进程号
import os
os.getpid() # 查看当前进程进程号
os.getppid() # 查看当前进程的父进程进程号
p.terminate() # 杀死当前进程
# 是告诉操作系统帮你去杀死当前进程 但是需要一定的时间 而代码的运行速度极快
time.sleep(0.1)
print(p.is_alive()) # 判断当前进程是否存活11.8 僵尸进程与孤儿进程(了解)
# 僵尸进程
"""
死了但是没有死透
当你开设了子进程之后 该进程死后不会立刻释放占用的进程号
因为我要让父进程能够查看到它开设的子进程的一些基本信息 占用的pid号 运行时间。。。
所有的进程都会步入僵尸进程
父进程不死并且在无限制的创建子进程并且子进程也不结束
回收子进程占用的pid号
父进程等待子进程运行结束
父进程调用join方法
"""
# 孤儿进程
"""
子进程存活,父进程意外死亡
操作系统会开设一个“儿童福利院”专门管理孤儿进程回收相关资源
"""11. 9 守护进程
from multiprocessing import Process
import time
def task(name):
print('%s总管正在活着'% name)
time.sleep(3)
print('%s总管正在死亡' % name)
if __name__ == '__main__':
p = Process(target=task,args=('egon',))
# p = Process(target=task,kwargs={'name':'egon'})
p.daemon = True # 将进程p设置成守护进程 这一句一定要放在start方法上面才有效否则会直接报错
p.start()
print('皇帝jason寿终正寝')11.10 互斥锁
多个进程操作同一份数据的时候,会出现数据错乱的问题
针对上述问题,解决方式就是加锁处理:将并发变成串行,牺牲效率但是保证了数据的安全
from multiprocessing import Process, Lock
import json
import time
import random
# 查票
def search(i):
# 文件操作读取票数
with open('data','r',encoding='utf8') as f:
dic = json.load(f)
print('用户%s查询余票:%s'%(i, dic.get('ticket_num')))
# 字典取值不要用[]的形式 推荐使用get 你写的代码打死都不能报错!!!
# 买票 1.先查 2.再买
def buy(i):
# 先查票
with open('data','r',encoding='utf8') as f:
dic = json.load(f)
# 模拟网络延迟
time.sleep(random.randint(1,3))
# 判断当前是否有票
if dic.get('ticket_num') > 0:
# 修改数据库 买票
dic['ticket_num'] -= 1
# 写入数据库
with open('data','w',encoding='utf8') as f:
json.dump(dic,f)
print('用户%s买票成功'%i)
else:
print('用户%s买票失败'%i)
# 整合上面两个函数
def run(i, mutex):
search(i)
# 给买票环节加锁处理
# 抢锁
mutex.acquire()
buy(i)
# 释放锁
mutex.release()
if __name__ == '__main__':
# 在主进程中生成一把锁 让所有的子进程抢 谁先抢到谁先买票
mutex = Lock()
for i in range(1,11):
p = Process(target=run, args=(i, mutex))
p.start()
"""
扩展 行锁 表锁
注意:
1.锁不要轻易的使用,容易造成死锁现象(我们写代码一般不会用到,都是内部封装好的)
2.锁只在处理数据的部分加来保证数据安全(只在争抢数据的环节加锁处理即可)
"""11.11 进程间通信
11.11.1 队列 Queue 模块
"""
管道:subprocess
stdin stdout stderr
队列:管道+锁
队列:先进先出
堆栈:先进后出
"""
from multiprocessing import Queue
# 创建一个队列
q = Queue(5) # 括号内可以传数字 标示生成的队列最大可以同时存放的数据量
# 往队列中存数据
q.put(111)
q.put(222)
q.put(333)
# print(q.full()) # 判断当前队列是否满了
# print(q.empty()) # 判断当前队列是否空了
q.put(444)
q.put(555)
# print(q.full()) # 判断当前队列是否满了
# q.put(666) # 当队列数据放满了之后 如果还有数据要放程序会阻塞 直到有位置让出来 不会报错
"""
存取数据 存是为了更好的取
千方百计的存、简单快捷的取
同在一个屋檐下
差距为何那么大
"""
# 去队列中取数据
v1 = q.get()
v2 = q.get()
v3 = q.get()
v4 = q.get()
v5 = q.get()
# print(q.empty())
# V6 = q.get_nowait() # 没有数据直接报错queue.Empty
# v6 = q.get(timeout=3) # 没有数据之后原地等待三秒之后再报错 queue.Empty
try:
v6 = q.get(timeout=3)
print(v6)
except Exception as e:
print('一滴都没有了!')
# # v6 = q.get() # 队列中如果已经没有数据的话 get方法会原地阻塞
# print(v1, v2, v3, v4, v5, v6)
"""
q.full()
q.empty()
q.get_nowait()
在多进程的情况下是不精确
"""11.11.2 IPC 机制
from multiprocessing import Queue, Process
"""
研究思路
1.主进程跟子进程借助于队列通信
2.子进程跟子进程借助于队列通信
"""
def producer(q):
q.put('我是23号技师 很高兴为您服务')
def consumer(q):
print(q.get()) ## '我是23号技师 很高兴为您服务'
if __name__ == '__main__':
q = Queue()
p = Process(target=producer,args=(q,))
p1 = Process(target=consumer,args=(q,))
p.start()
p1.start()11.11.3 生产者消费者模型
"""
生产者:生产/制造东西的
消费者:消费/处理东西的
该模型除了上述两个之外还需要一个媒介
生活中的例子做包子的将包子做好后放在蒸笼(媒介)里面,买包子的取蒸笼里面拿
厨师做菜做完之后用盘子装着给你消费者端过去
生产者和消费者之间不是直接做交互的,而是借助于媒介做交互
生产者(做包子的) + 消息队列(蒸笼) + 消费者(吃包子的)
"""
from multiprocessing import Process, Queue, JoinableQueue
import time
import random
def producer(name,food,q):
for i in range(5):
data = '%s生产了%s%s'%(name,food,i)
# 模拟延迟
time.sleep(random.randint(1,3))
print(data)
# 将数据放入 队列中
q.put(data)
def consumer(name,q):
# 消费者胃口很大 光盘行动
while True:
food = q.get() # 没有数据就会卡住
# 判断当前是否有结束的标识
# if food is None:break
time.sleep(random.randint(1,3))
print('%s吃了%s'%(name,food))
q.task_done() # 告诉队列你已经从里面取出了一个数据并且处理完毕了
if __name__ == '__main__':
# q = Queue()
q = JoinableQueue()
p1 = Process(target=producer,args=('大厨egon','包子',q))
p2 = Process(target=producer,args=('马叉虫tank','泔水',q))
c1 = Process(target=consumer,args=('春哥',q))
c2 = Process(target=consumer,args=('新哥',q))
p1.start()
p2.start()
# 将消费者设置成守护进程
c1.daemon = True
c2.daemon = True
c1.start()
c2.start()
p1.join()
p2.join()
# 等待生产者生产完毕之后 往队列中添加特定的结束符号
# q.put(None) # 肯定在所有生产者生产的数据的末尾
# q.put(None) # 肯定在所有生产者生产的数据的末尾
q.join() # 等待队列中所有的数据被取完再执行往下执行代码
"""
JoinableQueue 每当你往该队列中存入数据的时候 内部会有一个计数器+1
没当你调用task_done的时候 计数器-1
q.join() 当计数器为0的时候 才往后运行
"""
# 只要q.join执行完毕 说明消费者已经处理完数据了 消费者就没有存在的必要了11.12 线程
11.12.1 什么是线程
"""
进程:资源单位
线程:执行单位
将操作系统比喻成一个大的工厂
那么进程就相当于工厂里面的车间
而线程就是车间里面的流水线
每一个进程肯定自带一个线程
再次总结:
进程:资源单位(起一个进程仅仅只是在内存空间中开辟一块独立的空间)
线程:执行单位(真正被cpu执行的其实是进程里面的线程,线程指的就是代码的执行过程,执行代码中所需要使用到的资源都找所在的进程索要)
进程和线程都是虚拟单位,只是为了我们更加方便的描述问题
"""11.12.2 为何要有线程
"""
开设进程
1.申请内存空间 耗资源
2.“拷贝代码” 耗资源
开线程
一个进程内可以开设多个线程,在用一个进程内开设多个线程无需再次申请内存空间操作
总结:
开设线程的开销要远远的小于进程的开销
同一个进程下的多个线程数据是共享的!!!
"""
我们要开发一款文本编辑器
获取用户输入的功能
实时展示到屏幕的功能
自动保存到硬盘的功能
针对上面这三个功能,开设进程还是线程合适???
开三个线程处理上面的三个功能更加的合理11.12.3 开启线程的两种方式
## 第一种方法
from multiprocessing import Process
from threading import Thread
import time
def task(name):
print('%s is running'%name)
time.sleep(1)
print('%s is over'%name)
# # 开启线程不需要在main下面执行代码 直接书写就可以
# # 但是我们还是习惯性的将启动命令写在main下面
t = Thread(target=task,args=('egon',))
p = Process(target=task,args=('jason',))
p.start()
t.start() # 创建线程的开销非常小 几乎是代码一执行线程就已经创建了
print('主')
## 第二种方法
from threading import Thread
import time
class MyThead(Thread):
def __init__(self, name):
"""针对刷个下划线开头双下滑线结尾(__init__)的方法 统一读成 双下init"""
# 重写了别人的方法 又不知道别人的方法里有啥 你就调用父类的方法
super().__init__()
self.name = name
def run(self):
print('%s is running'%self.name)
time.sleep(1)
print('egon DSB')
if __name__ == '__main__':
t = MyThead('egon')
t.start()
print('主')11.12.4 TCP 协议实现并发
import socket
from threading import Thread
from multiprocessing import Process
"""
服务端
1.要有固定的IP和PORT
2.24小时不间断提供服务
3.能够支持并发
从现在开始要养成一个看源码的习惯
我们前期要立志称为拷贝忍者 卡卡西 不需要有任何的创新
等你拷贝到一定程度了 就可以开发自己的思想了
"""
server =socket.socket() # 括号内不加参数默认就是TCP协议
server.bind(('127.0.0.1',8080))
server.listen(5)
# 将服务的代码单独封装成一个函数
def talk(conn):
# 通信循环
while True:
try:
data = conn.recv(1024)
# 针对mac linux 客户端断开链接后
if len(data) == 0: break
print(data.decode('utf-8'))
conn.send(data.upper())
except ConnectionResetError as e:
print(e)
break
conn.close()
# 链接循环
while True:
conn, addr = server.accept() # 接客
# 叫其他人来服务客户
# t = Thread(target=talk,args=(conn,))
t = Process(target=talk,args=(conn,))
t.start()
"""客户端"""
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
client.send(b'hello world')
data = client.recv(1024)
print(data.decode('utf-8'))11.12.5 线程对象的 join 方法
from threading import Thread
import time
def task(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
if __name__ == '__main__':
t = Thread(target=task,args=('egon',))
t.start()
t.join() # 主线程等待子线程运行结束再执行
print('主')11.12.6,同一个进程下的多个线程数据是共享的
from threading import Thread
import time
money = 100
def task():
global money
money = 666
print(money) ### 666
if __name__ == '__main__':
t = Thread(target=task)
t.start()
t.join()
print(money) ## 66611.12.7 线程对象属性及其他方法
from threading import Thread, active_count, current_thread
import os,time
def task(n):
# print('hello world',os.getpid())
print('hello world',current_thread().name)
time.sleep(n)
if __name__ == '__main__':
t = Thread(target=task,args=(1,))
t1 = Thread(target=task,args=(2,))
t.start()
t1.start()
t.join()
print('主',active_count()) # 统计当前正在活跃的线程数
# print('主',os.getpid())
# print('主',current_thread().name) # 获取线程名字11.12.8 守护线程
from threading import Thread
import time
def task(name):
print('%s is running'%name)
time.sleep(1)
print('%s is over'%name) ## 这句话不会执行,主线程结束了以后,子线程也就结束了
if __name__ == '__main__':
t = Thread(target=task,args=('egon',))
t.daemon = True
t.start()
print('主')
"""
主线程运行结束之后不会立刻结束 会等待所有其他非守护线程结束才会结束
因为主线程的结束意味着所在的进程的结束
"""
# 稍微有一点迷惑性的例子
from threading import Thread
import time
def foo():
print(123)
time.sleep(1)
print('end123')
def func():
print(456)
time.sleep(3)
print('end456')
if __name__ == '__main__':
t1 = Thread(target=foo)
t2 = Thread(target=func)
t1.daemon = True ## ts设置成主线程死了以后立马死掉,但是由于主线程运行完了以后还要等待t2,所以不会立马死掉
t1.start()
t2.start()
print('主.......')
'''
123
456
主 ## 还要等待t2运行完,才会死掉
end123
end456
'''11.12.9 线程互斥锁
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()
if __name__ == '__main__':
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(money)11.12.10 GIL 全局解释器锁
"""
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)
"""
"""
python解释器其实有多个版本
Cpython
Jpython
Pypypython
但是普遍使用的都是CPython解释器
在CPython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行
同一个进程下的多个线程无法利用多核优势!!!
疑问:python的多线程是不是一点用都没有???无法利用多核优势
因为cpython中的内存管理不是线程安全的
内存管理(垃圾回收机制)
1.应用计数
2.标记清楚
3.分代回收
"""
"""
重点:
1.GIL不是python的特点而是CPython解释器的特点
2.GIL是保证解释器级别的数据的安全
3.GIL会导致同一个进程下的多个线程的无法同时执行即无法利用多核优势(******)
4.针对不同的数据还是需要加不同的锁处理
5.解释型语言的通病:同一个进程下多个线程无法利用多核优势
"""11.12.11 GIL 与普通互斥锁的区别
from threading import Thread,Lock
import time
mutex = Lock()
money = 100
def task():
global money
# with mutex:
# tmp = money
# time.sleep(0.1)
# money = tmp -1
mutex.acquire()
tmp = money
time.sleep(0.1) # 只要你进入IO了 GIL会自动释放
money = tmp - 1
mutex.release()
if __name__ == '__main__':
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(money)
"""
100个线程起起来之后 要先去抢GIL
我进入io GIL自动释放 但是我手上还有一个自己的互斥锁
其他线程虽然抢到了GIL但是抢不到互斥锁
最终GIL还是回到你的手上 你去操作数据
"""11.12.12 同一个进程下的多线程无法利用多核优势,是不是就没有用了
"""
多线程是否有用要看具体情况
单核:四个任务(IO密集型\计算密集型)
多核:四个任务(IO密集型\计算密集型)
"""
# 计算密集型 每个任务都需要10s
## 单核(不用考虑了)
## 多进程:额外的消耗资源
## 多线程:介绍开销
## 多核
## 多进程:总耗时 10+
## 多线程:总耗时 40+
# IO密集型
## 多核
## 多进程:相对浪费资源
## 多线程:更加节省资源12-1 代码验证
## 计算密集型
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res = 0
for i in range(10000000):
res *= i
if __name__ == '__main__':
l = []
print(os.cpu_count()) # 获取当前计算机CPU个数
start_time = time.time()
for i in range(12):
p = Process(target=work) # 1.4679949283599854
t = Thread(target=work) # 5.698534250259399
t.start()
# p.start()
# l.append(p)
l.append(t)
for p in l:
p.join()
print(time.time()-start_time)
# IO密集型
from multiprocessing import Process
from threading import Thread
import os,time
def work():
time.sleep(2)
if __name__ == '__main__':
l = []
print(os.cpu_count()) # 获取当前计算机CPU个数
start_time = time.time()
for i in range(4000):
# p = Process(target=work) # 21.149890184402466
t = Thread(target=work) # 3.007986068725586
t.start()
# p.start()
# l.append(p)
l.append(t)
for p in l:
p.join()
print(time.time()-start_time)11.12.13 总结
"""
多进程和多线程都有各自的优势
并且我们后面在写项目的时候通常可以
多进程下面再开设多线程
这样的话既可以利用多核也可以介绍资源消耗
"""11.13 死锁与递归锁(了解)
11.13.1 死锁
## 当你知道锁的使用抢锁必须要释放锁,其实你在操作锁的时候也极其容易产生死锁现象(整个程序卡死 阻塞)
from threading import Thread, Lock
import time
mutexA = Lock() ## 类不一样。产生的是不同的对象
mutexB = Lock()
# 类只要加括号多次 产生的肯定是不同的对象
# 如果你想要实现多次加括号等到的是相同的对象 单例模式
## 类实例化多次得到的都是同一个对象 ------------------- 单例模式
class MyThead(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('%s 抢到A锁'% self.name) # 获取当前线程名
mutexB.acquire()
print('%s 抢到B锁'% self.name)
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print('%s 抢到B锁'% self.name)
time.sleep(2)
mutexA.acquire()
print('%s 抢到A锁'% self.name) # 获取当前线程名
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t = MyThead()
t.start()11.13.2 递归锁
from threading import Thread, Lock,Rlock()
"""
递归锁的特点
可以被连续的acquire和release
但是只能被第一个抢到这把锁执行上述操作
它的内部有一个计数器 每acquire一次计数加一 每realse一次计数减一
只要计数不为0 那么其他人都无法抢到该锁
"""
# 将上述的
mutexA = Lock()
mutexB = Lock()
# 换成
mutexA = mutexB = RLock()
## 只要有人抢锁count加一,释放锁count减一,
## 如11.13.1 所示
def func1(self):
mutexA.acquire() ## AA抢到了 A锁 count = 1
print('%s 抢到A锁'% self.name) # 获取当前线程名
mutexB.acquire() ## AA抢到了 B锁 count = 2
print('%s 抢到B锁'% self.name)
mutexB.release() ## AA释放了 B锁 count = 1
mutexA.release() ## AA释放了 A锁 count = 0 count = 0 时其他人才可以抢,所以不会出现死锁
def func2(self):
mutexB.acquire()
print('%s 抢到B锁'% self.name)
time.sleep(2)
mutexA.acquire()
print('%s 抢到A锁'% self.name) # 获取当前线程名
mutexA.release()
mutexB.release()11.14 信号量(了解)
信号量在不同的阶段可能对应不同的技术点
在并发编程中信号量指的是锁!!!
"""
如果我们将互斥锁比喻成一个厕所的话
那么信号量就相当于多个厕所
"""
from threading import Thread, Semaphore
import time
import random
"""
利用random模块实现打印随机验证码(搜狗的一道笔试题)
"""
sm = Semaphore(5) # 括号内写数字 写几就表示开设几个坑位
def task(name):
sm.acquire()
print('%s 正在蹲坑'% name)
time.sleep(random.randint(1, 5))
sm.release()
if __name__ == '__main__':
for i in range(20):
t = Thread(target=task, args=('伞兵%s号'%i, ))
t.start()11.15 Event 事件(了解)
一些进程/线程需要等待另外一些进程/线程运行完毕之后才能运行,类似于发射信号一样
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)
if __name__ == '__main__':
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car, args=('%s'%i, ))
t.start()11.16 线程 Q(了解)
"""
同一个进程下多个线程数据是共享的
为什么先同一个进程下还会去使用队列呢
因为队列是
管道 + 锁
所以用队列还是为了保证数据的安全
"""
import queue
# 我们现在使用的队列都是只能在本地测试使用
# 1 队列q 先进先出
# q = queue.Queue(3)
# q.put(1)
# q.get()
# q.get_nowait()
# q.get(timeout=3)
# q.full()
# q.empty()
# 后进先出q
# q = queue.LifoQueue(3) # last in first out
# q.put(1)
# q.put(2)
# q.put(3)
# print(q.get()) # 3
# 优先级q 你可以给放入队列中的数据设置进出的优先级
q = queue.PriorityQueue(4)
q.put((10, '111'))
q.put((100, '222'))
q.put((0, '333'))
q.put((-5, '444'))
print(q.get()) # (-5, '444')
# put括号内放一个元祖 第一个放数字表示优先级
# 需要注意的是 数字越小优先级越高!!!11.17 进程池和线程池(掌握)
先回顾之前 TCP 服务端实现并发的效果是怎么玩的
每来一个人就开设一个进程或者线程去处理
"""
无论是开设进程也好还是开设线程也好 是不是都需要消耗资源
只不过开设线程的消耗比开设进程的稍微小一点而已
我们是不可能做到无限制的开设进程和线程的 因为计算机硬件的资源更不上!!!
硬件的开发速度远远赶不上软件呐
我们的宗旨应该是在保证计算机硬件能够正常工作的情况下最大限度的利用它
"""
# 池的概念
"""
什么是池?
池是用来保证计算机硬件安全的情况下最大限度的利用计算机
它降低了程序的运行效率但是保证了计算机硬件的安全 从而让你写的程序能够正常运行
"""11.17.1 线程池--进程池的基本使用
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os
# pool = ThreadPoolExecutor(5) # 池子里面固定只有五个线程
# 括号内可以传数字 不传的话默认会开设当前计算机cpu个数五倍的线程
pool = ProcessPoolExecutor(5)
# 括号内可以传数字 不传的话默认会开设当前计算机cpu个数进程
"""
池子造出来之后 里面会固定存在五个线程
这个五个线程不会出现重复创建和销毁的过程
池子造出来之后 里面会固定的几个进程
这个几个进程不会出现重复创建和销毁的过程
池子的使用非常的简单
你只需要将需要做的任务往池子中提交即可 自动会有人来服务你
"""
def task(n):
print(n,os.getpid())
time.sleep(2)
return n**n
## 回调函数
def call_back(n):
print('call_back>>>:',n.result()) ## 这里可以拿到结果
"""
任务的提交方式
同步:提交任务之后原地等待任务的返回结果 期间不做任何事
异步:提交任务之后不等待任务的返回结果 执行继续往下执行
返回结果如何获取???
异步提交任务的返回结果 应该通过回调机制来获取
回调机制
就相当于给每个异步任务绑定了一个定时炸弹
一旦该任务有结果立刻触发爆炸
"""
## 第一种:变运行任务,变打印结果
if __name__ == '__main__':
# pool.submit(task, 1) # 朝池子中提交任务 异步提交
# print('主')
t_list = []
for i in range(20): # 朝池子中提交20个任务
# res = pool.submit(task, i) # <Future at 0x100f97b38 state=running>
res = pool.submit(task, i).add_done_callback(call_back)
print(res.result()) # result方法 同步提交
t_list.append(res)
for t in t_list:
print('>>>:',t.result()) # 肯定是有序的
"""
程序有并发变成了串行
任务的为什么打印的是None:因为函数没有指定返回值
res.result() 拿到的就是异步提交的任务的返回结果
"""
## 第二种: 等待线程池中或进程池中所有的任务执行完毕之后再继续往下执行
if __name__ == '__main__':
t_list = []
for i in range(20): # 朝池子中提交20个任务
# res = pool.submit(task, i) # <Future at 0x100f97b38 state=running>
res = pool.submit(task, i).add_done_callback(call_back)
# 等待线程池中所有的任务执行完毕之后再继续往下执行
pool.shutdown() # 关闭线程池 等待线程池中所有的任务运行完毕
for t in t_list:
print('>>>:',t.result()) # 肯定是有序的
## 结果就是先运行完所有的任务,然后一起打印结果11.18.2 总结
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
pool = ProcessPoolExecutor(5)
pool.submit(task, i).add_done_callback(call_back)11.18 协程
"""
进程:资源单位
线程:执行单位
协程:这个概念完全是程序员自己意淫出来的 根本不存在
单线程下实现并发
我们程序员自己再代码层面上检测我们所有的IO操作
一旦遇到IO了 我们在代码级别完成切换
这样给CPU的感觉是你这个程序一直在运行 没有IO
从而提升程序的运行效率
多道技术
切换+保存状态
CPU两种切换
1.程序遇到IO
2.程序长时间占用
TCP服务端
accept
recv
代码如何做到
切换+保存状态
切换
切换不一定是提升效率 也有可能是降低效率
IO切 提升
没有IO切 降低
保存状态
保存上一次我执行的状态 下一次来接着上一次的操作继续往后执行
yield
"""11.18.1 验证切换是否就一定提升效率
import time
# # 串行执行计算密集型的任务 1.2372429370880127
def func1():
for i in range(10000000):
i + 1
def func2():
for i in range(10000000):
i + 1
start_time = time.time()
func1()
func2()
print(time.time() - start_time)
# 切换 + yield 2.1247239112854004
import time
def func1():
while True:
10000000 + 1
yield
def func2():
g = func1() # 先初始化出生成器
for i in range(10000000):
i + 1
next(g)
start_time = time.time()
func2()
print(time.time() - start_time)11.18.2 gevent 模块(了解)
pip3 install geventfrom gevent import monkey;monkey.patch_all()
import time
from gevent import spawn
"""
gevent模块本身无法检测常见的一些io操作
在使用的时候需要你额外的导入一句话
from gevent import monkey
monkey.patch_all()
又由于上面的两句话在使用gevent模块的时候是肯定要导入的
所以还支持简写
from gevent import monkey;monkey.patch_all()
"""
def heng():
print('哼')
time.sleep(2)
print('哼')
def ha():
print('哈')
time.sleep(3)
print('哈')
def heiheihei():
print('heiheihei')
time.sleep(5)
print('heiheihei')
start_time = time.time()
g1 = spawn(heng)
g2 = spawn(ha)
g3 = spawn(heiheihei)
g1.join()
g2.join() # 等待被检测的任务执行完毕 再往后继续执行
g3.join()
# heng()
# ha()
# print(time.time() - start_time) # 5.005702018737793
print(time.time() - start_time) # 3.004199981689453 5.00543904304504411.18.3 协程实现 TCP 服务端的并发
# 服务端
from gevent import monkey;monkey.patch_all()
import socket
from gevent import spawn
def communication(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0: break
conn.send(data.upper())
except ConnectionResetError as e:
print(e)
break
conn.close()
def server(ip, port):
server = socket.socket()
server.bind((ip, port))
server.listen(5)
while True:
conn, addr = server.accept()
spawn(communication, conn)
if __name__ == '__main__':
g1 = spawn(server, '127.0.0.1', 8080)
g1.join()
# 客户端
from threading import Thread, current_thread
import socket
def x_client():
client = socket.socket()
client.connect(('127.0.0.1',8080))
n = 0
while True:
msg = '%s say hello %s'%(current_thread().name,n)
n += 1
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
if __name__ == '__main__':
for i in range(500):
t = Thread(target=x_client)
t.start()11.18.4 总结
"""
理想状态:
我们可以通过
多进程下面开设多线程
多线程下面再开设协程序
从而使我们的程序执行效率提升
"""11.19 IO 模型
11.19.1 IO 模型简介
"""
我们这里研究的IO模型都是针对网络IO的
Stevens在文章中一共比较了五种IO Model:
* blocking IO 阻塞IO
* nonblocking IO 非阻塞IO
* IO multiplexing IO多路复用
* signal driven IO 信号驱动IO
* asynchronous IO 异步IO
由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model。
"""
#1)等待数据准备 (Waiting for the data to be ready)
#2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
## 同步异步
## 阻塞非阻塞
## 常见的网络阻塞状态:
accept
recv
recvfrom
## send虽然它也有io行为 但是不在我们的考虑范围11.19.2 阻塞 IO 模型
"""
我们之前写的都是阻塞IO模型 协程除外
"""
import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
conn, addr = server.accept()
while True:
try:
data = conn.recv(1024)
if len(data) == 0:break
print(data)
conn.send(data.upper())
except ConnectionResetError as e:
break
conn.close()
# 在服务端开设多进程或者多线程 进程池线程池 其实还是没有解决IO问题
## 该等的地方还是得等 没有规避
## 只不过多个人等待的彼此互不干扰11.19.3 非阻塞 IO
"""
要自己实现一个非阻塞IO模型
"""
import socket
import time
server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
# 将所有的网络阻塞变为非阻塞
server.setblocking(False)
r_list = []
del_list = []
while True:
try:
conn, addr = server.accept()
r_list.append(conn)
except BlockingIOError:
# time.sleep(0.1)
# print('列表的长度:',len(r_list))
# print('做其他事')
for conn in r_list:
try:
data = conn.recv(1024) # 没有消息 报错
if len(data) == 0: # 客户端断开链接
conn.close() # 关闭conn
# 将无用的conn从r_list删除
del_list.remove(conn)
continue
conn.send(data.upper())
except BlockingIOError:
continue
except ConnectionResetError:
conn.close()
del_list.append(conn)
# 挥手无用的链接
for conn in del_list:
r_list.remove(conn)
del_list.clear()
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8081))
while True:
client.send(b'hello world')
data = client.recv(1024)
print(data)11.19.4 总结
"""
虽然非阻塞IO给你的感觉非常的牛逼
但是该模型会 长时间占用着CPU并且不干活 让CPU不停的空转
我们实际应用中也不会考虑使用非阻塞IO模型
任何的技术点都有它存在的意义
实际应用或者是思想借鉴
"""11.19.5 IO 多路复用
"""
当监管的对象只有一个的时候 其实IO多路复用连阻塞IO都比比不上!!!
但是IO多路复用可以一次性监管很多个对象
server = socket.socket()
conn,addr = server.accept()
监管机制是操作系统本身就有的 如果你想要用该监管机制(select)
需要你导入对应的select模块
"""
import socket
import select
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
server.setblocking(False)
read_list = [server]
while True:
r_list, w_list, x_list = select.select(read_list, [], [])
"""
帮你监管
一旦有人来了 立刻给你返回对应的监管对象
"""
# print(res) # ([<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>], [], [])
# print(server)
# print(r_list)
for i in r_list: #
"""针对不同的对象做不同的处理"""
if i is server:
conn, addr = i.accept()
# 也应该添加到监管的队列中
read_list.append(conn)
else:
res = i.recv(1024)
if len(res) == 0:
i.close()
# 将无效的监管对象 移除
read_list.remove(i)
continue
print(res)
i.send(b'heiheiheiheihei')
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
client.send(b'hello world')
data = client.recv(1024)
print(data)11.19.6 总结
"""
监管机制其实有很多
select机制 windows linux都有
poll机制 只在linux有 poll和select都可以监管多个对象 但是poll监管的数量更多
上述select和poll机制其实都不是很完美 当监管的对象特别多的时候
可能会出现 极其大的延时响应
epoll机制 只在linux有
它给每一个监管对象都绑定一个回调机制
一旦有响应 回调机制立刻发起提醒
针对不同的操作系统还需要考虑不同检测机制 书写代码太多繁琐
有一个人能够根据你跑的平台的不同自动帮你选择对应的监管机制
selectors模块
"""11.19.7 异步 IO
"""
异步IO模型是所有模型中效率最高的 也是使用最广泛的
相关的模块和框架
模块:asyncio模块
异步框架:sanic tronado twisted
速度快!!!
"""
import threading
import asyncio
@asyncio.coroutine
def hello():
print('hello world %s'%threading.current_thread())
yield from asyncio.sleep(1) # 换成真正的IO操作
print('hello world %s' % threading.current_thread())
loop = asyncio.get_event_loop()
tasks = [hello(),hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()