协程
Event
-
用来控制线程的执行
-
有一些线程去控制另一些线程
-
Event对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生
-
初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。
-
一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程
-
线程特性: 每个线程都是独立运行且状态不可预测
from threading import Event
event.isSet() # 判断event 的状态值,
event.wait() # 当event.isSet() == Fadlse,将阻塞线程
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False
Timer : --定时器
from threading import Timer
def hello():
print('hello world')
t = Timer(9,hello) # 时间
t.start()
线程池 / 进程池:
-
问题 : 服务的开启的进程数或线程数都会随着并发的客户端数目地增多而增多,这会对服务端主机带来巨大的压力,甚至于不堪重负而瘫痪
-
对服务端开启的进程数或线程数加以控制,让机器在一个自己可以承受的范围内运行 ---》 基于多进程/线程 ,对数目加以限制
-
concurrent.futures 模块提供了高度封装的异步调用接口
ThreadPoolExecutor 线程池 ,提供异步调用
ProcessPoolExecutor 进程池,提供异步调用
1.submit(fn,*args,**kwargs)
-- 异步提交任务
2. map(func,*iterables,timeout = None,chunksize = 1)
-取代for 循环submit的操作
3.shutdown(wait=True)
-相当于进程池的pool.close()+pool.join()操作
4.wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前
result(timeout=None)
取得结果
5、add_done_callback(fn)
回调函数
#进程
from concurrent.futures import ThreadPoolExector,ProcessPoolExecutor
import os,time,random
def task(n):
print(f'{os.getpid()} is running') # 进程号
time.sleep(random.randint(1,3))
return n**2
if __name__ == '__main__':
executor = ProcessPoolExecutor(max_workers=3)
futures = []
for i in range(11):
future = executor.submit(task,i)
future.append(future)
executor.shutdown(True)
print('*' * 50)
for future in futures:
print(future.result())
#线程
--把ProcessPoolExecutor换成ThreadPoolExecutor,其余用法全部相同
回调函数:
- #### 可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务的返回值当作参数,该函数称为回调函数
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os
def get_page(url):
respone = request.get(url)
if response.status_code == 200:
return {'url':url,'text':response.text}
def parse_page(res):
res = res.result()
parse_res = 'url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
with open('db.txt','a') as f:
f.write(parse_res)
if __name__ == '__main__':
urls = [
'https://www.baidu.com',
'https://www.python.org',
'https://www.openstack.org',
'https://help.github.com/',
'http://www.sina.com.cn/'
]
p = ProcessPoolExecutor(3)
for url in urls:
p.submit(get_page,url).add_done_callback(parse_page)
协程:
-基于单线程来实现并发, 由用户程序自己控制调度的。、
-并发: 切换 + 保存状态 【IO / 任务运行时间过长】
-操作系统 : 进程 / 线程
-
代码调度 : 程序剪不断来回切换
-
协程程序模拟操作系统的多道技术 --》切换 + 保存状态
-
-避开操作系统的切换
-
实现自身能识别
-
实现协程 :
-
yield : 暂停 + 保存状态
-
send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换
-
gevent : 监听IO + 切换
-
计算密集型 --》降低效率
-
IO密集型 ----》 提高效率
-
Gevent 模块 :
- 可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度
from gevent import monkey # 放置文件头部
monkey.patch_all() # 监听程序内所有IO操作
from gevent import spawn,joinall ## 用于做切换 + 保存
g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
g2=gevent.spawn(func2)
g1.join() #等待g1结束
g2.join() #等待g2结束
#或者上述两步合作一步:gevent.joinall([g1,g2])
g1.value#拿到func1的返回值
DummyThread-n,即假线程
gevent实现单线程下的socket并发
fromfrom gevent import monkey;monkey.patch_all()
from socket import *
import gevent
#如果不想用money.patch_all()打补丁,可以用gevent自带的socket
# from gevent import socket
# s=socket.socket()
def server(server_ip,port):
s= socket()
s = setsockopt(SOL_SOCKET,SO_REUSADDR,1)
s.bind(server_ipp,port)
s.listen(5)
while True:
conn,addr = s.accept()
gevent.spawn(talk,conn,addr) #切换
def talk(conn,addr):
try:
while True:
res = conn.recv(1024)
conn.send(res.upper())
execpt Exception as e:
print(e)
finally:
conn.close()
if __name__ =='__main__':
server('127.0.0.1',8080)
多线程并发多个客户端
from threading import Thread
from socket import *
import threading
def client(server_ip,port):
c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了
c.connect((server_ip,port))
count=0
while True:
c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8'))
msg=c.recv(1024)
print(msg.decode('utf-8'))
count+=1
if __name__ == '__main__':
for i in range(500):
t=Thread(target=client,args=('127.0.0.1',8080))
t.start()