并发编程
1.必备知识回顾
1)计算机又叫电脑,即通电的大脑,发明计算机是为了让它通电之后能够像人一样去工作,并且它比人的工作效率更高,因为可以24小时不间断
2)计算机五大组成部分
控制器
运算器
存储器
输入设备
输出设备
计算机的核心真正干活的是CPU(控制器+运算器=中央处理器)
3)程序想要被计算机运行,它的代码必须要先由硬盘读到内存,之后cpu取指再执行
4)操作系统发展史
手工操作——穿孔卡片
手工操作——联机批处理系统
手工操作——脱机批处理系统
2.多道技术
单核实现并发的效果
并发:看起来像是同时运行的就可以称为并发
并行:真正意义上的同时运行
ps:并行肯定算是并发;单核的计算机肯定不能实现并行,但是可以实现并发(单核就是一个核,不考虑cpu里面的内核数)
重点知识:
1.空间上的复用和时间上的复用
空间上的复用:多个程序共用一套计算机硬件
时间上的复用:
例子1:洗衣服30s,做饭50s,烧水30s
单道需要110s,多道只需要任务最长的那个——>切换节省时间
例子2:边玩游戏边吃饭——>保持状态
切换+保存状态
2.切换(cpu)分为两种情况
1)当一个程序遇到IO操作的时候,操作系统会剥夺该程序的cpu执行权限
作用:提高了cpu的利用率,并且也不影响程序的执行效率
2)当一个程序长时间占用cpu的时候,操作系统也会剥夺该程序的cpu执行程序
作用:降低了程序的执行效率(原本时间+切换的时间)
3.进程理论
程序是一堆躺在硬盘上的代码,是“死”的
进程则表示程序正在执行的过程,是“活”的
进程调度
1)先来先服务调度算法:对长作业有利,对短作业无益
2)短作业优先调度算法:对短作业有利,对长作业无益
3)时间片轮转法+多级反馈队列
进程运行的三状态:就绪态,运行态,阻塞态
两对重要概念
1)同步和异步
描述的是任务的提交方式
同步:任务提交之后,原地等待任务的提交结果,等待的过程中不做任何事情(干等)
程序层面上表现出来的感觉就是卡住了
异步:任务提交之后,不原地等待任务的返回结果,直接去做其它事情
我提交的任务结果如何获取?任务的返回结果会有一个异步回调机制自动处理
2)阻塞非阻塞
描述程序的运行状态
阻塞:阻塞态
非阻塞:就绪态,运行态
理想状态:我们应该让我们写的代码永远处于就绪态和运行态直接切换
上述概念的组合:最高效的一种组合就是异步非阻塞
3)创建进程的两种方式
# 第一种
from multiprocessing import Process
import time
def task(name):
print('%s is running' %name)
time.sleep(3)
print('%s is over' %name)
if __name__ == '__main__':
# 1.创建一个对象
p = Process(target=task,args=('jason',))
# 容器类型哪怕里面只有一个元素,建议要用逗号隔开
# 2.开启进程
p.start() # 告诉操作系统给你创建一个进程,异步
print('主')
'''
windows操作系统下,创建进程一定要在main内创建
因为windows下创建进程类似于模块导入的方式,会从上往下依次执行代码
linux中则是直接将代码完整拷贝一份
'''
# 第二种方式
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('主')
总结:
创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去
一个进程对应在内存中就是一块独立的内存空间
多个进程对应在内存中就是多块独立的内存空间
进程与进程之间默认情况下是无法直接交互的,如果想要交互需要借助于第三方工具,模块
4)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__':
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() # 主进程等待子进程p运行结束之后再继续往后执行
print('主',time.time()-start_time)
5)默认情况下进程间数据隔离
from multiprocessing import Process
money = 100
def task():
global money # 局部修改全局
money = 666
print('子',money)
if __name__ == '__main__':
p = Process(target=task)
p.start()
p.join()
print(money)
6)进程对象及其方法
如何查看?
windows电脑:1.进入cmd输入tasklist即可查看;2.tasklist |findstr PID查看具体的进程
mac电脑:1.进入终端之后输入ps aux即可查看;2.ps aux | grep PID 查看具体的进程
from multiprocessing import Process,current_process
import time
import os
def task():
# print('%s is running'%current_process().pid) # 查看当前进程的进程号
print('%s is running' % os.getpid()) # 查看当前进程的进程号
print('子进程的主进程号%s'%os.getppid())
time.sleep(3)
if __name__ == '__main__':
p = Process(target=task)
p.start()
# print('主',current_process().pid)
print('主',os.getpid())
print('主主',os.getppid()) # 获取父进程的pid号
7)僵尸进程与孤儿进程
僵尸进程:死了没死透,当你开设了子进程之后,该进程死后不会立刻释放占用的进程号。因为我要让父进程能够查看到它开设的子进程的一些基本信息,占用的pid号,运行时间等。所有的进程都会步入僵尸进程,父进程不死并且在无限制地创建子进程并且子进程也不结束。回收子进程占用的pid号:父进程等待子进程运行结束,父进程调用join方法
孤儿进程:子进程存活,父进程意外死亡。操作系统会开设一个“儿童福利院”专门管理孤儿进程回收相关资源
8)守护进程
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.daemon = True # 将进程pid设置成守护进程,这一句一定要放在start之前否则报错
p.start()
print('皇帝jason寿终正寝')
9)互斥锁
多个进程操作同一份数据的时候,会出现数据错乱的问题
针对上述问题,解决方式就是加锁处理,将并发变成串行,牺牲效率但是保证数据的安全
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
# 买票 先查票,再购买
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.锁只在处理数据的部分加来保证数据安全(只在争抢数据的环节加锁处理即可)
10)队列
管道: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('数据取完了')
# print(v1,v2,v3,v4,v5,v6) # 队列中如果已经没有数据的话,get方法会原地阻塞
'''
q.full()
q.empty()
q.get_nowait()
在多进程情况下是不精确的
'''
11)IPC机制
from multiprocessing import Queue,Process
'''
研究思路
1.主进程跟子进程借助于队列通信
2.子进程跟子进程借助于队列通信
'''
def producer(q):
q.put('我是23号技师,很高兴为您服务')
def consumer(q):
print(q.get())
if __name__ == '__main__':
q = Queue()
p = Process(target=producer,args=(q,))
p1 = Process(target=consumer,args=(q,))
p.start()
p1.start()
12)生产者消费者模型
生产者:生产/制造东西的
消费者:消费/处理东西的
该模型除了上述两个之外还需要一个媒介:
生活中的例子做包子的将包子做好后放在蒸笼(媒介)里面,买包子的取蒸笼里面拿
厨师做菜做完之后用盘子装着给你消费者端过去
生产者和消费者之间不是直接做交互的,而是借助于媒介做交互
生产者(做包子的) + 消息队列(蒸笼) + 消费者(吃包子的)
# coding:utf-8
from multiprocessing import Queue,Process,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() # 等待队列中所有的数据被取完再执行往下执行代码
# 只要q.join执行完毕 说明消费者已经处理完数据了,消费者就没有存在的必要了
'''
JoinableQueue每当你往该队列中存入数据的时候 内部会有一个计数器+1
当你调用task_done的时候 计数器-1
q.join()当计算器为0的时候 才往后运行
'''
4.线程理论
1)什么是线程
进程:资源单位
线程:执行单位
将操作系统比喻成一个大的工厂,那么进程就相当于工厂里面的车间,而线程就是车间里面的流水线,每一个进程肯定自带一个线程。
再次总结:
进程:资源单位(起一个进程仅仅是在内存空间中开辟一块独立的空间)
线程:执行单位(真正被cpu执行的其实是进程里面的线程,线程指的就是代码的执行过程,执行代码中所需要使用到的资源都找所在的进程索要)
进程和线程都是虚拟单位,只是为了我们更加方便地描述问题
2)为何要有线程
开设进程:
1.申请内存空间 耗资源
2.“拷贝代码” 耗资源
开线程:
一个进程内可以开设多个线程,在用一个进程内开设多个线程无需再次申请内存空间操作
总结:
开设线程的开销要远远小于开设进程的开销
同一个进程下的多个进程数据是共享的
我们要开发一款文本编辑器
获取用户输入的功能
实时展示到屏幕的功能
自动保存到硬盘的功能
针对上面这三个功能,开设进程还是开设线程合适?答:开三个线程处理上面的三个功能更加合理
3)如何使用
1.tcp实现服务端并发效果
# coding:utf-8
import socket
from threading import Thread
from multiprocessing import Process
'''
服务端
1.要有固定的ip和端口
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 ConnectionError 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()
客户端代码:
# coding:utf-8
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'))
2.线程对象的join方法
# coding:utf-8
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('主')
3.同一个进程下多个线程数据共享
# coding:utf-8
from threading import Thread
import time
money = 100
def task():
global money
money = 666
if __name__ == '__main__':
t = Thread(target=task)
t.start()
t.join()
print(money)
4.线程对象及其它方法
# coding:utf-8
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()
t1.join()
print('主',active_count()) # 统计当前正在活跃的线程数
# print('主',os.getpid())
# print('主',current_thread().name) # 获取线程名字
5.守护线程
# coding:utf-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
t1.start()
t2.start()
print('主.....')
6.GIL全局解释器锁理论
# coding:utf-8
# GIL全局解释器锁
'''
常见的pycharm解释器:Cpython,Jpython,Pypypython
但是常用的都是Cpython解释器
在cpython解释器中GIL是一把互斥锁,用来阻止同一个进程下多个线程同时执行
同一个进程下多个线程无法利用多核优势
疑问:python的多线程岂不是没有作用?无法利用多核优势
因为cpython中的内存管理不是线程安全的
内存管理(垃圾回收机制)
1.引用计数
2.标记清除
3.分代回收
'''
'''
重点:
1.GIL不是python的特点而是cpython解释器的特点
2.GIL是保证解释器级别的数据安全
3.GIL会导致同一个进程下的多个线程无法同时执行,即无法利用多核优势(***)
4.针对不同的数据还是需要加不同的锁处理
5.解释型语言的通病:同一个进程下多个线程无法利用多核优势
'''
# GIL与其它锁同时存在的情况下
from threading import Thread,Lock
import time
money = 100
mutex = Lock()
def task():
global money
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还是回到你的手上,你去操作数据
'''
7.多进程与多线程比较
# coding:utf-8
'''
同一个进程下的多线程无法利用多核优势,是不是就没有用了?
多线程是否有用需要看具体情况
单核:四个任务(IO密集型/计算密集型)
多核:四个任务(IO密集型/计算密集型)
# 计算密集型 每个任务都需要10s
单核(不用考虑了)
多进程:额外的消耗资源
多线程:节省开销
多核
多进程:总耗时 10+
多线程:总耗时 40+
# io密集型
多核
多进程:相对浪费资源
多线程:更加节省资源
'''
# 代码验证
# 计算密集型
# from multiprocessing import Process
# from threading import Thread
# import os,time
#
# def work():
# res = 0
# for i in range(1000000):
# res *= i
#
# if __name__ == '__main__':
# l = []
# print(os.cpu_count()) # 获取当前计算机cpu个数
# start_time = time.time()
# for i in range(4):
# p = Process(target=work) # 1.260631799697876
# t = Thread(target=work) # 0.2623305320739746
# # p.start()
# t.start()
# l.append(t)
# # l.append(p)
# 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())
start_time = time.time()
for i in range(90):
p = Process(target=work) # 20.567763566970825
t = Thread(target=work) # 2.0278875827789307
t.start()
# p.start()
# l.append(p)
l.append(t)
for p in l:
p.join()
print(time.time()-start_time)
# 总结
'''
多进程和多线程都有各自优势
并且在后面写项目的时候通常可以
多进程下面再开设多线程
这样的话就可以利用多核也可以节省资源消耗
'''
8.死锁与递归锁
# coding:utf-8
# 当你知道锁的使用枪锁必须要释放锁,其实你在操作锁的时候也极其容易产生死锁现象(整个程序卡死,阻塞)
# 死锁现象
from threading import Thread,Lock,RLock
import time
# mutexA = Lock()
# mutexB = Lock() # 死锁
mutexA = mutexB = RLock() # 递归锁
# 类只要加括号多次,产生的肯定是不同的对象
# 如果你想要实现多次加括号等到的是相同的对象,单例模式
'''
递归锁的特点
可以被连续的acquire和release
但是只能被第一个抢到这把锁执行上述操作
它的内部有一个计数器 每acquire一次计数器加一,每release一次计数器减一
只要计数不为0,那么其它人都无法抢到该锁
'''
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()
9.信号量
# coding:utf-8
from threading import Thread,Semaphore
import time
import 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()
10.Event事件
# coding:utf-8
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.线程q
# coding:utf-8
'''
同一个进程下多个线程数据是共享的,为什么同一个进程下还会去使用队列呢?
因为队列是
管道+锁
所以用队列还是为了保证数据的安全
'''
# 我们现在使用的队列都是只能在本地测试使用
# 1.队列q,先进先出
import queue
# q = queue.Queue(3)
# q.put(1)
# print(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括号内放一个元组,第一个放数字表示优先级
# 需要注意的是 数字越小优先级越高
12.进程池与线程池
# coding:utf-8
'''
无论是开设线程还是进程,都需要消耗资源,只不过开设线程的消耗比开设进程的稍微小一点而已
我们不可能做到无限制地开设线程和进程,因为计算机的硬件资源跟不上
我们的宗旨应该是在保证计算机硬件能够正常工作的情况下最大限度地利用它
什么是池:
池是用来保证计算机硬件安全的情况下最大限度地利用计算机
它降低了程序的运行效率但是保证了计算机硬件的安全,从而让你写的程序能够正常运行
'''
import os
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import time
import os
# pool = ThreadPoolExecutor(5) # 池子里面固定只有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 0x2b4d8871700 state=pending>
res = pool.submit(task,i).add_done_callback(call_back)
# print(res.result()) # result方法 同步提交
# t_list.append(res)
# 等待线程池中所有任务执行完毕之后再往后继续执行
# pool.shutdown() # 关闭线程池 等待线程池中所有的任务运行完毕
# for t in t_list:
# print('>>>:',t.result()) # 肯定是有序的
'''
任务由并发变成了串行
任务结果为什么打印的是None
'''
'''
总结:
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
pool = ProcessPoolExecutor(5)
res = pool.submit(task,i).add_done_callback(call_back)
'''
13.协程基本原理
进程:资源单位
线程:执行单位
协程:这个概念是程序员自己意淫出来的,完全不存在
单线程下实现并发:
我们程序员自己在代码层面上检测我们所有的IO操作,一旦遇到了IO,我们在代码级别完成切换。这样给CPU的感觉就是你这个程序一直在运行,没有IO,从而提升程序的执行效率
多道技术:
切换+保存状态
CPU两种切换
1.程序遇到IO
2.程序长时间占用
代码实现:
# coding:utf-8
# import time
#
# # 串行执行计算密集型的任务 1.4062752723693848
# 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 3.5455222129821777
# 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)
# gevent模块
from 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('嘿嘿嘿')
time.sleep(5)
print('嘿嘿嘿')
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.009616374969482
print(time.time()-start_time) # 3.0842819213867188 5.054822206497192
协程实现TCP并发
服务端:
# coding:utf-8
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 ConnectionError 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',9999)
g1.join()
客户端
# coding:utf-8
from threading import Thread,current_thread
import socket
def x_client():
client = socket.socket()
client.connect(('127.0.0.1',9999))
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(100):
t = Thread(target=x_client)
t.start()
总结:
理想状态:我们可以通过多进程下面开设多线程,多线程下面再开设协程序,从而使我们的程序执行效率提升
14.IO类型简介
这里研究的IO模型都是针对网络IO的
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)
网络并发知识点梳理:
1.软件开发架构
2.互联网协议
osi七层
五层
每一层是干嘛的
以太网协议,广播风暴
IP协议
TCP/UDP
3.三次握手四次挥手
4.socket简介
5.TCP粘包问题 定制固定长度的报头
6.UDP协议
7.socketserver模块
8.操作系统发展史
9.多道技术
10.进程理论
11.开启进程的两种方式
12.互斥锁
13.生产者消费者模型
14.线程理论
15.开启线程的两种方式
16.GIL全局解释器
17.进程池线程池
18.协程的概念
19.IO模型的了解
人工智能相关参考网站
http://www.turingapi.com/
https://www.xfyun.cn/?ch=bd05-105&bd_vid=12070905557395409840
https://ai.baidu.com/
作为一名python程序员当你遇到一个功能的时候,第一时间你可以考虑是否有对应的模块已经帮你实现了该功能
如果pycharm老是过期,可以直接下载最新版本的pycharm,然后进入一个网址获取激活码即可
https://www.ajihuo.com
datagrip激活码获取