目录
- 一、进程简介
- 1.什么是并发、并行?
- 2.什么是同步异步?
- 3.什么是阻塞非阻塞
- 4.程序的理想状态
- 二、进程
- 1.创建进程原理:
- 2.进程中的方法(大概)
- 3.创建进程的两种方式
- 1.第一种方式,实例化Process方法
- 2.第二种方式,继承Process类重写run方法
- 4.进程之间是隔离的(可以通过其他方式交互)
- 5.守护进程p.daemon = True
- 6.杀死进程,判断存活
- 7.孤儿进程,僵尸进程
- 8.父进程回收子进程资源(进程号)的两种方式
- 三、进程与进程的通讯
- 1.队列中的基本方法
- 2.队列、堆栈、优先级队列
- 1.队列生成
- 2.堆栈生成
- 3.优先级队列生成
- 3.队列之间的通讯
- 4.生产者与消费者模型
- 四、线程
- 1.线程简介
- 1.什么是线程
- 2.为什么要有线程
- 3.同一个进程下的多个线程本来就是数据共享,为什么还要用队列?
- 2.创建线程的两种方式
- 1.第一种第一种实例化Thread方法
- 2.第二种,继承Thread类重写run方法
- 3.线程之间的资源是共享的
- 4.守护线程
- 5.线程之间的通讯
- 6.event事件
- 1.线程简介
- 五.锁
- 1.进程互斥锁
- 01买票,查票例子
- 2.线程互斥锁
- **多个数据操作同一份文件,第一时间应该想到上锁
- 3.死锁
- 4.递归锁
- 5.GIL全局解释器锁
- 6.信号量锁
- 1.进程互斥锁
- 六、进程池与线程池
- 1.什么是池?
- 2.线程池进程池?
- 3.异步提交submit
- 4.回调函数add_done_callback()
- 七、简单的多线程案列
- 八、协程
- 1.协程的创建
- 2.TCP单线程实现并发
一、进程简介
1.什么是并发、并行?
并发:看起来像同时运行,内部:服务器来回切换者服务,速度很快,用户感觉不到
并行:真正意义上的同时运行
2.什么是同步异步?
同步异步:表示任务的提交方式
同步:任务提交之后,必须等待有返回结果,才继续执行下一步
异步:任务提交之后,不等待,继续执行下一步,(返回结果通过其他方式调用)
3.什么是阻塞非阻塞
阻塞:阻塞态
非阻塞:就绪态,运行态
4.程序的理想状态
异步非阻塞
二、进程
windows 创建进程会将代码以模块的方式 从上往下执行一遍
linux 会直接将代码完完整整的拷贝一份
windows 创建进程一定要在 if __name__ == '__main__':代码内创建 否则报错
在windows操作系统中,由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会字典Import启动它的这个文件,而在import的时候又执行了整个文件。一次如果将process()直接写在文件中就会无限提柜创建子进程报错。所以必须把创建子进程的部分使用 if __name__ == '__main__':判断保护起来,import的时候,就不会递归运行了
1.创建进程原理:
1.创建进程就是在内存中重新开辟一块内存空间
将允许产生的代码丢进去
2.一个进程对应在内存就是一块独立的内存空间
3.进程与进程之间数据是隔离的 无法直接交互
但是可以通过某些技术实现间接交互
2.进程中的方法(大概)
from multiprocessing import Process : 创建进程模块
p.start() # 告诉操作系统帮你创建一个进程,操作系统随机创建
p.join() # 主进程代码等待子进程运行结束
current_process() # 进程号(用os的)
os.getpid() # 获取子进程号
os.getppid() # 获取父进程号
p.terminate() # 杀死进程号
p.is_alive() # 判断进程是否存在(bool值)
p.daemon = Trun # 将主进程 设置为守护进程(放在创建进程的前面 p.start)
Lock模块:加锁
mutex.acquire() # 抢锁
mutex.release() # 释放锁
metex = Lock() # 创建锁
3.创建进程的两种方式
关键字:from multiprocessing import Process 进程模块
Process(group=None, target=None, name=None, args=(), kwargs={})
强调:
1.需要使用关键字的方式来指定参数
2.args指定为target函数位置传参,是一个元组的形式,所以必须加','逗号
创建进程:通过定义的方法或者类创建子进程对象,然后再创建进程用 对象.start()方法
参数介绍:
1.group参数未使用,始终为None
2.target表示调用对象,即子进程要执行的任务
3.args表示调用对象的位置参数元组,args=('jeff',)
4.kwargs表示调用对象的字典,kwargs = {'name': 'kkk', 'age': 18}
5.name为子进程的名称
1.第一种方式,实例化Process方法
from multiprocessing import Process
import time
def test(name):
print('%s 子进程开始' % name)
time.sleep(2)
print('%s 子进程结束' % name)
if __name__ == '__main__':
p = Process(target=test, args=('jeff',)) # 创建一个子进程对象
p.start() # 告诉操作系统帮你创建一个进程
print('主程序')
2.第二种方式,继承Process类重写run方法
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print('%s 子进程开始' % self.name)
time.sleep(2)
print('%s 子进程结束' % self.name)
if __name__ == '__main__':
p = MyProcess('jeff') # 创建进程对象
p.start() # 创建进程
p.join() # 等待创建的子进程结束,再执行下面的代码
print('主进程 ')
4.进程之间是隔离的(可以通过其他方式交互)
# 进程之间是隔离的
from multiprocessing import Process
n = 100
def work():
global n
n = 11
print('子进程内', n) # 结果11,验证了进程之间是隔离的
if __name__ == '__main__':
p = Process(target=work)
p.start()
p.join() # 等待子进程结束啦=了,在执行下面的代码
print('主进程内:', n)
# 结果:
子进程内 11
主进程内: 100
5.守护进程p.daemon = True
守护进程p.daemon = True:主进程结束时,子进程立马结束,这句话必须放在start()创建进程 前面
p = Process(target=test,args=('QQ',)) # 创建子进程对象
from multiprocessing import Process
import time
"""
守护进程:主进程结束,子进程立马结束
"""
def test(name):
print('%s 子进程活着' % name)
time.sleep(2)
print('%s 子进程死亡' % name)
if __name__ == '__main__':
p = Process(target=test,args=('QQ',)) # 创建子进程对象
p.daemon = True # 将该进程设置为守护 进程 这句话必须放在start()前面
p.start() # 创建进程
time.sleep(1)
print('qq主进程结束')
6.杀死进程,判断存活
p.terminate() : 杀死当前进程
p.is_alive() :判断当前进程是否存活,返回值为bool类型
from multiprocessing import Process,current_process
import os
import time
def test(name):
# print('%s is running'%name,current_process().pid)
print('%s is running'%name,'子进程%s'%os.getpid(),'父进程%s'%os.getppid())
time.sleep(3)
print('%s is over'%name)
if __name__ == '__main__':
p = Process(target=test,args=('egon',))
p.start()
p.terminate() # 杀死当前进程 其实是告诉操作系统帮你杀死一个进程
time.sleep(0.1)
print(p.is_alive()) # 判断进程是否存活
# print('主',current_process().pid)
print('主',os.getpid(),'主主进程:%s'%os.getppid())
7.孤儿进程,僵尸进程
一个电脑的进程号有限制的,每一个程序启动都会占用一个进程号
init称为孤儿福利院
僵尸进程:
在子进程结束后,主进程没有正常结束, 子进程PID不会被回收。危害:致系统不能产生新的进程
- 操作系统中的PID号是有限的,如有子进程PID号无法正常回收,则会占用PID号。
- 资源浪费。
- 若PID号满了,则无法创建新的进程。
孤儿进程:
在子进程没有结束时,主进程没有“正常结束”, 子进程PID不会被回收。等子进程结束,由init孤儿院回收
在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
8.父进程回收子进程资源(进程号)的两种方式
1.join方法
2.父进程正常死亡
三、进程与进程的通讯
queue:队列模块
joinablequeue:可等待的队列模块
队列(FIFO):先进先出(买包子,先排队先买)
堆栈(FILO):先进后出(衣柜放衣服,先放进去,后拿出来)
1.队列中的基本方法
from multiprocessing import Queue
q = Queue(5) # 生成队列对象,括号内的数字表示 这个队列最大储存数
q.put(1) # 往队列中添加数据1,
当队列满了,再放数据,不会报错,程序原地等待(阻塞态),等待有位置放值
q.get() # 取值,因为先进先出,所以先取第一个值
当队列取完之后,再次取值,程序原地等待(阻塞态),等待有值取
q.get_nowait() # 取值不等待(阻塞),没有了直接报错
q.put_nowait() # 添加值不等待(阻塞),满了直接报错
q.full() # 判断队列是否满了(bool值)
q.empty() # 判断队列是否为空(bool值)
q.join() # 等待
Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
Queue.join() 实际上意味着等到队列为空,再执行别的操作
如果线程里每从队列里取一次,但没有执行task_done(),则join无法判断队列到底有没有结束,在最后执行个join()是等不到结果的,会一直挂起。
可以理解为,每task_done一次 就从队列里删掉一个元素,这样在最后join的时候根据队列长度是否为零来判断队列是否结束,从而执行主线程。
2.队列、堆栈、优先级队列
队列:先进先出 q = queue.Queue()
堆栈:先进后出 q = queue.LifoQueue()
优先级队列:数字越小,优先级越高 q = queue.PriorityQueue()
1.队列生成
q = queue.Queue()
q.put('hahha')
print(q.get())
结果:hahha
2.堆栈生成
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())
结果: 3 先进后出
3.优先级队列生成
q = queue.PriorityQueue()
# 数字越小 优先级越高
q.put((10,'haha'))
q.put((100,'hehehe'))
q.put((0,'xxxx'))
q.put((-10,'yyyy'))
print(q.get())
结果:(-10, 'yyyy')
3.队列之间的通讯
例子1:
from multiprocessing import Process,Queue
"""
创建两个子进程,让一个子进程放数据,另一个子进程拿数据
"""
def producer(q):
q.put('hello jeff')
def consumer(q):
print(q.get())
if __name__ == '__main__':
q = Queue() # 生成一个队列
p = Process(target=producer, args=(q,)) # 创建一个生产进程对象
c = Process(target=consumer, args=(q,)) # 创建一个消费进程对象
p.start() # 创建进程,启动
c.start() # 创建进程,启动
# 结果:
hello jeff
例子2:
from multiprocessing import Process
from multiprocessing import JoinableQueue
import time
def task1(q):
x = 100
q.put(x) # 1步,存
print('添加数据', x)
time.sleep(3)
print(q.get()) # 4步,取
def task2(q):
# 想要在task2中获取task1的x
res = q.get() # 2步,取
print(f'获取的数据是{res}')
q.put(9527) # 3步,存
if __name__ == '__main__':
# 产生队列
q = JoinableQueue(10)
# 产生两个不同的子进程
p1 = Process(target=task1, args=(q, ))
p2 = Process(target=task2, args=(q, ))
p1.start()
p2.start()
# 结果:
添加数据 100
获取的数据是100
9527
4.生产者与消费者模型
"""
生产者:生产/制造数据的
消费者:消费/处理数据的
例子:做包子的,买包子的
1.做包子远比买包子的多
2.做包子的远比包子的少
供需不平衡的问题
"""
from multiprocessing import Process,Queue,JoinableQueue
import random
import time
def producer(name, food, q):
for i in range(5):
data = '%s生产了%s%s'%(name,food,i)
time.sleep(random.random()) # 等待0-1秒
q.put(data) # 往队列里放
print(data)
def consumer(name,q):
while True:
data = q.get() # 从队列里取
if data == None:break
print('%s吃了%s'%(name,data))
time.sleep(random.random())
q.task_done() # 告诉队列你已经从队列中取出了一个数据 并且处理完毕了
if __name__ == '__main__':
q = JoinableQueue()
p = Process(target=producer, args=('辜友银','奥利奥',q))
p1 = Process(target=producer, args=('cherry','狮子头',q))
c = Process(target=consumer, args=('陈嘉旻',q))
c1 = Process(target=consumer, args=('jeff',q))
p.start()
p1.start()
c.daemon = True # 设置守护进程
c1.daemon = True
c.start()
c1.start()
p.join()
p1.join()
q.join() # 等到队列中数据全部取出
四、线程
1.线程简介
程序最大效率化(节省内存空间):开进程(数量为cpu数量)---->进程里面开线程---->线程里面开协程
1.什么是线程
内存是工厂,进程是车间,线程是流水线
进程、线程其实都是虚拟单位,都是用来帮助我们更加形象的迷哦书某种事物
进程:资源单位(数据隔离)
线程:执行单位(数据共享)
ps:每个进程都自带一个线程,线程才是真正的执行单位,进程只是再线程中提供资源(车间提供资源给流水线)
2.为什么要有线程
开进程:1.申请内存空间 耗资源
2.进程从上至下执行代码,‘拷贝代码’ 耗资源
**开线程:1.一个进程内可以开多个线程,并且线程之间的数据是共享的。(一个车间内可以有多个流水线,但是一个车间的资源都是共享的)
ps:开启线程的开销远远小于开启进程的开销
3.同一个进程下的多个线程本来就是数据共享,为什么还要用队列?
因为队列是 管道+锁 使用队列你就不需要自己手动操作锁的问题
锁操作的不好极容易产生死锁现象
2.创建线程的两种方式
关键字:from threading import Thread 线程模块
注意:与创建进程的两种方式一样
1.第一种第一种实例化Thread方法
# 第一种第一种实例化Thread方法
def task(name):
print('%s 开启了' % name)
time.sleep(3)
print('%s 结束了' % name)
if __name__ == '__main__':
t = Thread(target=task, args=('jeff',)) # 创建子线程对象
t.start() # 告诉操作系统开启这个线程
print('主线程')
2.第二种,继承Thread类重写run方法
# 第二种,继承Thread类重写run方法
from threading import Thread
import time
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print('%s 开启了' % self.name)
time.sleep(3)
print('%s 关闭了' % self.name)
t = MyThread('jeff')
t.start()
print('主线程')
3.线程之间的资源是共享的
一个进程里面可以有很多线程,一个进程中的资源都是共享的。一个车间可以有很多流水线,都是用的同一个车间的资源。
4.守护线程
关键字:子线程对线.daemon = True 设置该 子线程 为 主线程的 守护线程
主线程结束,守护线程立马结束
注意:主线程结束,意味着进程结束,资源销毁;但是其他非守护线程还没有结束,还在使用资源,所以应该等待其他非守护线程结束之后,主线程才结束,进程才结束
from threading import Thread,current_thread
import time
def task(i):
print(current_thread().name) # 打印子线程的名字
time.sleep(i)
print('子线程结束')
t = Thread(target=task,args=(1,))
t.daemon = True # 设置为守护线程
t.start() # 启动
print('主线程')
5.线程之间的通讯
from threading import Thread
money = 100
def task():
global money # 申明全局变量
money = 999 # 修改全局
t = Thread(target=task) # 创建线程对象
t.start() # 启动
t.join() # 等待t子线程执行结束
print(money) # 修改成功
#结果:999
6.event事件
模块: from threading import Event
e = Event() # 生成一个Event对象
e.set() # 发信号
e.wait # 等待信号
例子:等待红绿灯
from threading import Event,Thread
import time
# 先生成一个event对象
e = Event()
def light():
print('红灯正亮着')
time.sleep(3)
e.set() # 发信号
print('绿灯亮了')
def car(name):
print('%s正在等红灯'%name)
e.wait() # 等待接收信号,信号接收到立马执行下一步
print('%s加油门飙车了'%name)
t = Thread(target=light)
t.start()
for i in range(10):
t = Thread(target=car,args=('小明%s'%i,))
t.start()
五.锁
1.进程互斥锁
多个进程同时间操作同一份文件,会造成数据絮乱
处理方法:加锁处理,将并发变成串行,虽然降低了效率,但是提高了安全
注意:1.锁不要轻易使用,容易造成死锁现象
2.只在处理关键数据的部分加锁
3.锁必须在主进程中产生,交给子进程使用
Lock模块:加锁
mutex.acquire() # 抢锁
mutex.release() # 释放锁
metex = Lock() # 创建锁,生成一把锁
01买票,查票例子
"""
买票思路:
一.用户查询余票
1.每个用户都可以同时查询余票,但是在查票买票的这一过程别人也可以买票
二.用户买票
1.如果有余票,则进入买票阶段
2.用互斥锁,把数据锁上,同一时间只有一个用户可以操作数据
"""
from multiprocessing import Process, Lock
import time
import json
# 查票
def search(name):
with open('data', 'r', encoding='utf-8') as f:
data = f.read()
data_d = json.loads(data) # 解序列化
print('用户%s 查询余票为:%s' % (name, data_d.get('ticket')))
# 买票
def buy(name):
with open('data', 'r', encoding='utf-8') as f:
data = f.read()
data_d = json.loads(data)
time.sleep(1) # 模拟网络延迟
if data_d.get('ticket') > 0: # 如果票数大于0就可以买票
# 票数减一
data_d['ticket'] -= 1
# 更新票数,重新写入文件 文件操作
with open('data', 'w', encoding='utf-8') as f:
json.dump(data_d, f) # 转序列化
print('用户%s抢票成功' % name)
else:
print('没票了')
def run(name, mutex):
search(name)
mutex.acquire() # 抢锁
buy(name)
mutex.release() # 释放锁
if __name__ == '__main__':
mutex = Lock() # 生成一把锁
for i in range(10):
p = Process(target=run, args=(i, mutex))
p.start()
2.线程互斥锁
当多个用户同一时间操作同一份数据的时候,容易造成数据错乱
**多个数据操作同一份文件,第一时间应该想到上锁
from threading import Thread,Lock
import time
"""
多个线程同一时间操作同一份文件,容易造成数据错乱
"""
n =100
def task(mutex):
global n
mutex.acquire() # 抢锁
tmp = n
time.sleep(0.01)
n = tmp-1
mutex.release() # 释放锁
t_list = []
mutex = Lock() # 生成锁
for i in range(100):
t = Thread(target=task, args=(mutex,))
t.start()
t_list.append(t) # 子线程名字添加到列表
for t in t_list:
t.join() # 确保子线程都执行完毕
print(n)
3.死锁
两把锁的时候:你锁我,我锁你。。。。死锁
例子说明:
from threading import Thread,Lock
import time
mutexA = Lock() # 生成A锁
mutexB = Lock() # 生成B锁
class MyThread(Thread):
def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
self.func1()
self.func2()
def func1(self):
mutexA.acquire() # 1号抢到A锁,其他人在外面
# self.name等价于current_thread().name
print('%s抢到了A锁'%self.name)
mutexB.acquire() # 其他人在外面,1号抢到A锁继续抢到B锁
print('%s抢到了B锁'%self.name)
mutexB.release() # 1号释放了B锁
print('%s释放了B锁'%self.name)
mutexA.release() # 1号释放了A锁,这个时候2号抢到A锁
print('%s释放了A锁'%self.name)
def func2(self):
mutexB.acquire() # 1号继续抢到B锁
print('%s抢到了B锁'%self.name)
time.sleep(1)
mutexA.acquire()# 这时,1号要抢A锁,但是A锁在2号手上,2号要抢B锁,B锁在1号手上,死锁
print('%s抢到了A锁' % self.name)
mutexA.release()
print('%s释放了A锁' % self.name)
mutexB.release()
print('%s释放了B锁' % self.name)
for i in range(10): # 10个人抢锁
t = MyThread() # 生成线程对象
t.start() # 启动线程
4.递归锁
RLock 递归锁模块
acquire 抢锁
release 释放锁
RLock可以被第一个抢到锁的人连续的acquire和release
每acquire一次锁身上的计数加1
每release一次锁身上的计数减1
只要锁的计数不为0 其他人都不能抢
from threading import Thread,Lock
import time
mutexA = mutexB = RLock() # A B现在是同一把锁
class MyThread(Thread):
def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name
mutexB.acquire()
print('%s抢到了B锁'%self.name)
mutexB.release()
print('%s释放了B锁'%self.name)
mutexA.release()
print('%s释放了A锁'%self.name)
def func2(self):
mutexB.acquire()
print('%s抢到了B锁'%self.name)
time.sleep(1)
mutexA.acquire()
print('%s抢到了A锁' % self.name)
mutexA.release()
print('%s释放了A锁' % self.name)
mutexB.release()
print('%s释放了B锁' % self.name)
for i in range(10):
t = MyThread()
t.start()
5.GIL全局解释器锁
ps: python解释器有很多种 最常见的就是Cpython解释器
GIL本质也是一把互斥锁:将并发变成串行牺牲效率保证数据的安全
用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发)
python的多线程没法利用多核优势 是不是就是没有用了?
GIL的存在是因为CPython解释器的内存管理不是线程安全的
垃圾回收机制(gc)(内存管理)
1.引用计数 : 值与变量绑定关系的个数 (一个变量有几个名字,名称空间)
2.标记清除 : 当内存快要满的时候 ,会自动停止程序的运行 从上往下,检测所有的变量与值的绑定关系,给没有绑定关系的值打上标记, 最后一次性清楚 (python内部机制,自动触发)
3.分代回收 :垃圾回收机制也是一块功能,是功能就需要消耗。程序内部有一部分类似与常量,减少垃圾回收的消耗,应该对变量与值的绑定关系做一个分类。新生代>>>青春代>>>老年代 ,垃圾回收机制扫描一档次数发现关系还在,会将该对关系移至下一代,随着待数的递增,扫描频率是降低的
同一个进程下能否多个线程同时运行?:不能
GIL就相当于加在解释器上的锁,多线程同时抢这把锁,谁先抢到谁先执行:
研究python的多线程是否有用需要分情况讨论
四个任务 计算密集型的 10s
单核情况下
开线程更省资源
多核情况下
开进程 10s
开线程 40s
四个任务 IO密集型的
单核情况下
开线程更节省资源
多核情况下
开线程更节省资源
例子:计算密集型
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res=0
for i in range(100000000):
res*=i
if __name__ == '__main__':
l=[]
print(os.cpu_count()) # 本机为6核
start=time.time()
for i in range(6):
# p=Process(target=work) #耗时 4.732933044433594
p=Thread(target=work) #耗时 22.83087730407715
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('r
例子:IO密集型
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
time.sleep(2)
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为6核
start=time.time()
for i in range(40):
p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上
# p=Thread(target=work) #耗时2.051966667175293s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
6.信号量锁
模块 from threading import Semaphore
信号量在不同的领域中,对应不同的知识点
互斥锁:一个厕所一个坑,
信号量:一个厕所多个坑,必须等有空位置,下一个人才能进去。一次可容纳多个人
例子:40个人排队上5个坑位的公共厕所
from threading import Semaphore,Thread
import time
import random
sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所
def task(name):
sm.acquire()
print('%s占了一个坑位'%name)
time.sleep(random.randint(1,3))
sm.release()
for i in range(40): # 40人
t = Thread(target=task,args=(i,)) # 生成线程对象
t.start() # 启动
六、进程池与线程池
开线程的资源虽然少,但是架不住量大
在计算机能够承受范围之内最大限度的利用计算机
1.什么是池?
在包装计算机硬件安全的情况下最大限度的利用计算机
池其实降低的程序的运行效率,但是保证了硬件的安全
因为硬件的发展跟不上软件的发展
2.线程池进程池?
提交任务的方式?
1.同步:提交任务后,等待返回结果 期间不做任何事
2.异步:提交任务后,不等待返回结果(异步回调),直接执行下一行代码
异步提交:池.submit(函数名,参数)
异步回调:n.result() 当异步提交的任务有返回结果之后,会自动触发回调函数的执行
注意:这里的n代表的是提交函数的返回结果
# 提交任务的时候,绑定一个回调函数 一旦该任务有结果 立刻执行对应的回调函数
pool = ProcessPoolExecutor() # 创建进程池 默认当前计算机的cpu的个数
pool = ThreadPoolExecutor(5) # 创建线程池 括号内可以传参数指定线程池的线程的个数,不传默认cpu个数*5
pool.submit(task, i) # 朝池中提交任务 异步提交
print('拿到了异步提交的返回结果',n.result())
3.异步提交submit
提交任务的时候,绑定一个回调函数 一旦该任务有结果 立刻执行对应的回调函数
res = pool.submit(task, i).add_done_callback(call_back)
4.回调函数add_done_callback()
提交任务的时候,绑定一个回调函数 一旦该任务有结果 立刻执行对应的回调函数
res = pool.submit(task, i).add_done_callback(call_back)
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import os
import time
def task(n):
print(n,os.getpid()) # 查看当前进程号
time.sleep(1)
return n+100
def call_back(n):
print('拿到了异步提交的返回结果',n.result())
os.cpu_count() # 查看当前计算机的cup数量
pool = ProcessPoolExecutor() # 默认当前计算机的cpu的个数
# pool = ThreadPoolExecutor(5) # 括号内可以传参数指定线程池的线程的个数,不传默认cpu个数*5
if __name__ == '__main__':
t_list = []
for i in range(10):
# 提交任务的时候,绑定一个回调函数 一旦该任务有结果 立刻执行对应的回调函数
res = pool.submit(task, i).add_done_callback(call_back)
t_list.append(res)
七、简单的多线程案列
客户端:
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
client.send(b'hello')
data = client.recv(1024)
print(data.decode('utf-8'))
服务端:
import socket
from threading import Thread
"""
服务端
1.要有固定的IP和PORT
2.24小时不间断提供服务
3.能够支持并发
"""
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
# 封装的通信功能
def talk(conn):
while True:
try:
data = conn.recv(1024)
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() # 监听 等待客户端的连接 阻塞态
print(addr)
t = Thread(target=talk,args=(conn,))
t.start()
八、协程
单线程下实行并发
进程:资源单位
线程:执行单位
协程:单线程下实行并发,(欺骗操作系统,让操作系统误认为一直没有IO,运行态和就绪态切换)
切换+保存状态不一定提升效率?
1.IO密集型 提升效率
2.计算密集型 降低效率
并发本质:切换+保存状态
ps:看起来像同时执行,称之并发
协程:完全是程序员自己想出来的名词,单线程下实现并发
并发的条件?
多道技术
空间上的复用
时间上的复用:切换——保存状态(IO操作)
1.协程的创建
检测有没有IO的模块:genent 模块
monkey : 猴子补听,监听
检测所有的任务,来回切换:spawn 配合join来使用
注意:gevent模块没办法自动识别time.sleep,等io情况,需要手动在配置一个参数,monkey.patch_all()
from gevent import monkey;monkey.patch_all() # 由于该模块经常被使用 所以建议写成一行
from gevent import spawn
import time
def heng():
print('哼')
time.sleep(1)
print('哼')
def ha():
print('哈')
time.sleep(3)
print('哈')
def hei():
print('嘿')
time.sleep(5)
print('嘿')
# 异步 5.011743068695068
start = time.time()
a1 = spawn(heng) # spawn会检测所有的任务,来回切换,实际只有一个线程来回的切换完成所有的任务
a2 = spawn(ha)
a3 = spawn(hei)
a1.join()
a2.join()
a3.join()
print(time.time()-start)
# 同步 9.013060092926025
# start = time.time()
# a1 = heng()
# a2 = ha()
# a3 = hei()
# print(time.time()-start)
2.TCP单线程实现并发
客户端:
import socket
from threading import Thread,current_thread
def client():
client = socket.socket()
client.connect(('127.0.0.1', 8080))
n = 0
while True:
data = '%s %s' % (current_thread().name,n)
client.send(data.encode('utf-8'))
res = client.recv(1024)
print(res.decode('utf-8'))
n += 1
for i in range(10):
t = Thread(target=client)
t.start()
服务端:
from gevent import monkey;monkey.patch_all()
import socket
from gevent import spawn
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
def talk(conn):
while True:
try:
data = conn.recv(1024)
print(data.decode('utf-8'))
conn.send(data.upper())
except BaseException as f:
print(f)
break
conn.close()
def server1():
while True:
conn, addr = server.accept()
spawn(talk,conn)
if __name__ == '__main__':
a1 = spawn(server1) # spawn会检测所有的任务,来回切换,配合join使用
a1.join()