Event事件

event 事件用来控制线程的执行,

由一些线程去控制另一些线程。

使用threading库中的Event对象,对象中包含了一个可由线程设置的信号标志,允许线程等待某些事件的发生

在初始情况下,Event对象中的信号标志位False,如果有线程等待一个Event对象,这个Event对象的标志为假,那么这个线程将会被一直阻塞直至标志为真。一个线程如果将一个Event对象信号标志设置为真,它将唤醒所有等待这个Event对象的线程。

# coding=utf-8

from threading import  Event
from threading import Thread
import time # 调用类实例化出对象
e = Event() # 若程序中有如下代码,即为False,阻塞
# e.wait() # 若程序中有如下代码,则将其他线程的False改为True,进入就绪态和运行态
# e.set() # 模拟一个红绿灯
def light():
print("红灯亮")
time.sleep(5) # 开始发信号给其他线程,告诉其他线程准备执行
e.set()
print("绿灯亮") # 模拟一个个汽车
def car():
print("正在等红灯")
e.wait()
print("汽车开始起步") t1 = Thread(target=light)
t1.start() for i in range(10):
t2 = Thread(target=car)
t2.start() 红灯亮
正在等红灯
正在等红灯
正在等红灯
正在等红灯
正在等红灯
正在等红灯
正在等红灯
正在等红灯
正在等红灯
正在等红灯 绿灯亮
汽车开始起步
汽车开始起步
汽车开始起步
汽车开始起步
汽车开始起步
汽车开始起步
汽车开始起步
汽车开始起步
汽车开始起步
汽车开始起步

e.wait():False,为阻塞状态

e.set():True,将其他线程的False改为True,进入就绪态和运行态

e.clear():回复event的状态值为False

e.isSet():返回event的状态值

例如,有多个工作线程尝试链接MySQL,我们想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器,如果连接不成功,都会去尝试重新连接。那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作

from threading import Thread,Event
import threading
import time,random
def conn_mysql():
count=1
while not event.is_set():
if count > 3:
raise TimeoutError('链接超时')
print('<%s>第%s次尝试链接' % (threading.current_thread().getName(), count))
event.wait(0.5)
count+=1
print('<%s>链接成功' %threading.current_thread().getName())
def check_mysql():
print('\033[45m[%s]正在检查mysql\033[0m' % threading.current_thread().getName())
time.sleep(random.randint(2,4))
event.set()
if __name__ == '__main__':
event=Event()
conn1=Thread(target=conn_mysql)
conn2=Thread(target=conn_mysql)
check=Thread(target=check_mysql)
conn1.start()
conn2.start()
check.start()

线程池 进程池

在刚开始学多进程或多线程时,我们迫不及待地基于多进程或多线程实现并发的套接字通信,然而这种实现方式的致命缺陷是:服务的开启的进程数或线程数都会随着并发的客户端数目地增多而增多,这会对服务端主机带来巨大的压力,甚至于不堪重负而瘫痪,于是我们必须对服务端开启的进程数或线程数加以控制,让机器在一个自己可以承受的范围内运行,这就是进程池或线程池的用途,例如进程池,就是用来存放进程的池子,本质还是基于多进程,只不过是对开启进程的数目加上了限制

进程池和线程池:

用来控制当前程序允许创建(进程/线程)的数量

进程池和线程池的作用:

保证在硬件允许的范围内创建(进程/线程)的数量

# coding=utf-8

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time # 进程池可以加参数 表示开启进程数
# 若不写默认以CPU的个数限制进程数
# ProcessPoolExecutor() # 线程池可以加参数,表示开启的线程数
# 若不写默认以CPU 的个数 *5 限制线程数
# ThreadPoolExecutor() # 创建5个线程
pool = ThreadPoolExecutor(5) def task(res):
print("线程任务开始")
time.sleep(1)
# print("线程任务结束")
return 123 # 回调函数
def call_back(res):
# print(type(res))
# 注意 回调函数接收一个参数 是 接收线程执行完的结果,用.result()接收
# 得到的数据可以拿一个变量名保存,新的变量名不要与回调函数参数一样
res2 = res.result()
print(res2) for i in range(13):
# 异步提交任务,每次并发执行最多只能有5个
pool.submit(task,1).add_done_callback(call_back) # 所有线程任务结束后执行下面代码
pool.shutdown()
print("线程执行完毕了")

from concurrent.futures:提供了异步调用的接口

ProcessPoolExecutor():限制开启的进程数,若不写参数默认以CPU的个数限制进程数

ThreadPoolExecutor():限制开启的线程数,若不写参数默认以CPU的个数 * 5 限制线程数

pool.submit(函数名,参数):异步提交任务,限制每次并发执行最多的线程个数

add_done_callback:回调函数,线程执行完毕的函数返回值可以传到回调函数中,.result()获取线程执行返回的结果

pool.shutdown():所有线程任务执行完毕后执行线程池关闭,执行下面的代码,相当于进程池的pool.close()+pool.join()操作

进程池:

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import os,time,random
def task(n):
print('%s is runing' %os.getpid())
time.sleep(1)
return n**2 if __name__ == '__main__':
executor=ProcessPoolExecutor(max_workers=3)
futures=[]
for i in range(11):
future=executor.submit(task,i)
futures.append(future)
executor.shutdown(True)
print('+++>')
for future in futures:
print(future.result())

回调函数

可以为进程/线程池内的每个进程/线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收线程任务的返回值当做参数,该函数就叫做回调函数

高性能爬取梨视频

from concurrent.futures import ThreadPoolExecutor
import requests
import re
import uuid pool = ThreadPoolExecutor(200) # 1.发送请求函数
def get_page(url):
response = requests.get(url)
return response # 2.解析主页获取视频ID号
def parse_index(response):
id_list = re.findall('<a href="video_(.*?)".*?>',response.text,re.S)
return id_list # 3.解析视频详情页获取真实 视频链接
def parse_detail(res):
response = res.result()
movie_detail_url = re.findall('srcUrl="(.*?)"', response.text, re.S)[0]
print(f'往视频链接: {movie_detail_url}发送请求...') # 异步往视频详情页链接发送请求,把结果交给
pool.submit(get_page, movie_detail_url).add_done_callback(save_movie)
return movie_detail_url # 4.往真实视频链接发送请求,获取数据并保存到本地
def save_movie(res):
movie_response = res.result()
name = str(uuid.uuid4())
print(f'{name}.mp4视频开始保存...')
with open(f'{name}.mp4', 'wb') as f:
f.write(movie_response.content)
print('视频下载完毕!') if __name__ == '__main__':
# 1.访问主页获取数据
index_response = get_page('https://www.pearvideo.com/')
# # 2.解析主页获取所有的视频id号
id_list = parse_index(index_response)
# 3.循环对每个视频详情页链接进行拼接
for id in id_list:
detail_url = 'https://www.pearvideo.com/video_' + id
# 异步提交爬取视频详情页,把返回的数据,交给parse_detail(回调函数)
pool.submit(get_page, detail_url).add_done_callback(parse_detail)

协程

进程:资源单位

线程:执行单位

协程:在单线程下实现并发

协程即 基于单线程来实现并发,即只用一个主线程的情况下实现并发,是一种用户态的轻量级线程,是由用户程序自己控制调度的一张程序。

并发的概念:切换 + 保存状态

cpu正在运行一个任务,会有两种情况下切走去执行其他任务(切换操作由操作系统强制控制即多道技术),一种情况是该任务发生了阻塞,另外一种情况是该任务执行时间过长,cpu会把使用权切断。

yield保存状态

yield 代码级别的控制,可以保存当前状态

# 基于yield 并发执行
import time
# 任务1:接收数据,处理数据
def cousumer():
while True:
x = yield def producer():
g = cousumer()
next(g)
for i in range(10000000):
g.send(i)
# time.sleep(1) # 并发去执行,但是如果遇到IO就会阻塞住
# 并不会切换到该线程内其他任务去执行 start = time.time()
# 基于yield保存状态,实现两个任务直接来回切换,即并发的效果
producer()
stop = time.time()
print(stop - start) # 2.9251673221588135

以上例子对纯计算密集型任务来说,对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。

总结协程特点:

1、 必须在只有一个单线程里实现并发

2、 修改共享数据不需要枷锁

3、 用户程序里自己保存多个控制流的上下文栈

gevent模块

from gevent import monkey
from gevent import spawn,joinall
import time # gevent 是一个第三方模块,可以帮你监听IO操作,并切换
# 监听该程序下所有的IO操作
monkey.patch_all() def func1():
print("1")
# 模拟IO操作
time.sleep(1) def func2():
print("2")
time.sleep(2) def func3():
print("3")
time.sleep(3) start = time.time() # 实现单线程下,遇到IO,保存状态 + 切换
s1 = spawn(func1)
s2 = spawn(func2)
s3 = spawn(func3) # 发送信号,在单线程情况下相当于等待自己执行完毕之后再退出
joinall([s1,s2,s3])
end_time = time.time() print(end_time - start) # 6.013344049453735

协程的目的

通过手动模拟操作系统“多道技术”,实现切换 + 保存状态

优点:在IO密集型的情况下会提高效率

缺点:在计算密集型的情况下,来回切换,反而效率更低

如何实现协程:切换 + 保存状态

yield:保存状态

并发:切换

TCP服务端单线程下实现并发

server端:
# coding=utf-8 from gevent import monkey
monkey.patch_all() import socket
import time
from threading import Thread
from gevent import spawn,joinall server = socket.socket()
server.bind(("127.0.0.1",8888))
server.listen(5)
print("启动服务端...") # 线程任务,接收客户端消息与发送消息给客户端
def work1(conn):
while True:
try:
data = conn.recv(1024).decode("utf-8")
if not data:break conn.send(data.encode("utf-8"))
except Exception as e:
print(e)
break
conn.close() def work2():
while True:
conn,addr = server.accept()
spawn(work1,conn) if __name__ == '__main__':
s1 = spawn(work2)
s1.join()
client端:
# coding=utf-8 import socket
import time
from threading import Thread,current_thread def client():
client = socket.socket()
client.connect(("127.0.0.1",8888))
print("启动客户端...")
num = 0
while True:
send_data = f"{current_thread().name} {num}"
client.send(send_data.encode("utf-8"))
data = client.recv(1024)
print(data.decode("utf-8"))
num += 1 for i in range(30):
t = Thread(target=client)
t.start()

Python-线程(3)-协程的更多相关文章

  1. python线程、协程、I/O多路复用

    目录: 并发多线程 协程 I/O多路复用(未完成,待续) 一.并发多线程 1.线程简述: 一条流水线的执行过程是一个线程,一条流水线必须属于一个车间,一个车间的运行过程就是一个进程(一个进程内至少一个 ...

  2. Python线程和协程-day10

    写在前面 上课第10天,打卡: 感谢Egon老师细致入微的讲解,的确有学到东西! 一.线程 1.关于线程的补充 线程:就是一条流水线的执行过程,一条流水线必须属于一个车间: 那这个车间的运行过程就是一 ...

  3. python 线程 进程 协程 学习

    转载自大神博客:http://www.cnblogs.com/aylin/p/5601969.html 仅供学习使用···· python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和 ...

  4. Python线程和协程

    写在前面 好好学习 天天向上 一.线程 1.关于线程的补充 线程:就是一条流水线的执行过程,一条流水线必须属于一个车间: 那这个车间的运行过程就是一个进程: 即一个进程内,至少有一个线程: 进程是一个 ...

  5. Python线程和协程CPU资源利用率测试

    前言介绍 协程 ,又称为微线程,它是实现多任务的另一种方式,只不过是比线程更小的执行单元.因为它自带CPU的上下文,这样只要在合适的时机,我们可以把一个协程切换到另一个协程.通俗的理解: 在一个线程中 ...

  6. Python学习之路--进程,线程,协程

    进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Q ...

  7. Python—进程、线程、协程

    一.线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 方法: ...

  8. 11.python之线程,协程,进程,

    一,进程与线程 1.什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行 ...

  9. python中socket、进程、线程、协程、池的创建方式和应用场景

    进程 场景 利用多核.高计算型的程序.启动数量有限 进程是计算机中最小的资源分配单位 进程和线程是包含关系 每个进程中都至少有一条线程 可以利用多核,数据隔离 创建 销毁 切换 时间开销都比较大 随着 ...

  10. python 进程、线程与协程的区别

    进程.线程与协程区别总结 - 1.进程是计算器最小资源分配单位 - 2.线程是CPU调度的最小单位 - 3.进程切换需要的资源很最大,效率很低 - 4.线程切换需要的资源一般,效率一般(当然了在不考虑 ...

随机推荐

  1. LED 发光二极管压降

    常用发光二极管的压降 1. 直插超亮发光二极管压降 主要有三种颜色,然而三种发光二极管的压降都不相同,具体压降参考值如下: 红色发光二极管的压降为2.0--2.2V  黄色发光二极管的压降为1.8—2 ...

  2. 活动:月末送Java技术书福利|抽奖

    本公众号运营了快一年了 原创干货超过200+ 收获了也快1W粉丝 这么多粉丝-- 送书活动怎能少? 虽然这次我们是有备而来 但是-- 所有书籍为作者自掏腰包 所以本次送书数量有限 不能满足到所有人 重 ...

  3. java-day13

    异常 指的是程序在执行过程中,出现的非正常情况,最终会导致JVM的非正常停止 异常分类:编译异常,运行期异常 异常的产生过程分析 throw关键字:指方法中抛出指定异常 使用格式:throw new ...

  4. POJ 3525 /// 半平面交 模板

    题目大意: 给定n,接下来n行逆时针给定小岛的n个顶点 输出岛内离海最远的点与海的距离 半平面交模板题 将整个小岛视为由许多半平面围成 那么以相同的比例缩小这些半平面 一直到缩小到一个点时 那个点就是 ...

  5. 请求参数MD5加密---函数助手

  6. ASP.NET网站要手机自适应页面

    本文转载的地址:http://mobile.51cto.com/ahot-409516.htm 一. 允许网页宽度自动调整: "自适应网页设计"到底是怎么做到的? 其实并不难. 首 ...

  7. ubuntu 权限不够,解决办法,无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用)

    终端执行  sudo passwd root输入root 新密码执行命令  nano /usr/share/lightdm/lightdm.conf.d/50-ubuntu.conf末行添加   gr ...

  8. leetcode-227-基本计算器②

    题目描述: 方法一:中缀转后缀 #!_*_coding:utf-8_*_ class Solution: def calculate(self, s: str) -> int: def in_t ...

  9. [JZOJ6279] 2019.8.5【NOIP提高组A】优美序列

    题目 题目大意 给你一个排列以及若干区间,对于每个区间,问包含它的最小的优美序列的区间. 所谓优美序列,即将权值排序后能够得到连续的排列. 思考历程 优美序列显然满足这个条件:\(mx-mn=r-l\ ...

  10. Divide by Zero 2018 and Codeforces Round #474 (Div. 1 + Div. 2, combined)G - Bandit Blues

    题意:求满足条件的排列,1:从左往右会遇到a个比当前数大的数,(每次遇到更大的数会更换当前数)2.从右往左会遇到b个比当前数大的数. 题解:1-n的排列,n肯定是从左往右和从右往左的最后一个数. 考虑 ...