python多线程异步(一)
一直想写一个多线程博客,汇总一下方老师教给我们的知识。但是因为一直没有用到,或者自己还没有吃透里面的精髓,所以不敢下笔。现在工作中又遇到必须要通过多线程解决的问题,所以再回顾以前方老师的课程,从头整理一下多线程异步这块知识,方便以后遇到问题可以快速写出代码来。
目录
- 1、多线程异步初步介绍
- 1.1一般的异步demo
- 1.2傀儡线程
- 2、线程锁
- 2.1、为什么要锁
- 2.2、不加锁代码
- 2.3、加锁代码
- 3、条件锁
- 3.1、条件锁代码
1、多线程异步初步介绍
串行和异步模式如下图,从图上可以很直观看出串行变成和异步编程区别
python 中线程需要用到自带的Threading包,可以使用并发
1.1一般的异步demo
import time
import threadingdef syn_method():#串行的普通编程方式print("Start")time.sleep(1)print("Visit website 1")time.sleep(1)print("Visit website 2")time.sleep(1)print("Visit website 3")print("End")def asyn_method():#异步多线程方式print("Start")def visit1():#注意,这里是在asyn_method函数内部定义visit1函数,在python中是允许这样写的time.sleep(1)print("Visit website 1")def visit2():time.sleep(1)print("Visit website 2")def visit3():time.sleep(1)print("Visit website 3")th1 = threading.Thread(target=visit1)#首先定义多线程th2 = threading.Thread(target=visit2)th3 = threading.Thread(target=visit3)th1.start()#其次运行线程,相当于快速按下线程开关,线程启动后继续异步向下执行下面代码th2.start()th3.start()th1.join()#最后汇总,等待线程1完成th2.join()th3.join()print("End")asyn_method()#异步多线程函数
# syn_method()#同步串行函数
上面这个代码包含了线程定义、线程开始、线程阻塞执行三个要点。
1.2傀儡线程
在异步执行的时候,主线程执行完毕后(子线程没有join等待完成),有些子线程可能还没有启动,这时候就需要在主线程执行完毕后把所有没用启动或者正在执行的线程都杀死,避免造成系统垃圾。这时候就要用到傀儡线程,如果设置了傀儡线程,当主线程执行完毕后傀儡线程会自动关闭
傀儡线程的设置很简单,只需要在设置线程的时候增加daemon=True即可
把上面代码做了一下改进,1、把join()阻塞代码注释掉;2、在定义线程的时候增加daemon=True;3把sleep时间增加到10秒,便于更好的实验观察
代码如下:
import time
import threadingdef syn_method():#串行的普通编程方式print("Start")time.sleep(1)print("Visit website 1")time.sleep(1)print("Visit website 2")time.sleep(1)print("Visit website 3")print("End")def asyn_method():#异步多线程方式print("Start")def visit1():#注意,这里是在asyn_method函数内部定义visit1函数,在python中是允许这样写的time.sleep(10)print("Visit website 1")def visit2():time.sleep(10)print("Visit website 2")def visit3():time.sleep(10)print("Visit website 3")th1 = threading.Thread(target=visit1,daemon=True)#设置daemon=True,把线程定义成傀儡线程,当主线程结束后,傀儡线程自动关闭,而不会在后台运行th2 = threading.Thread(target=visit2,daemon=True)th3 = threading.Thread(target=visit3,daemon=True)th1.start()#其次运行线程,相当于快速按下线程开关,线程启动后继续异步向下执行下面代码th2.start()th3.start()#th1.join()#最后汇总,等待线程1完成#th2.join()#th3.join()print("End")asyn_method()#异步多线程函数
# syn_method()#同步串行函数
增加了傀儡设置后,运行结果如下
各个子线程的结果没用输出,因为在等待的过程中主线程已经执行完毕了,各个子线程被杀死了。
实验二:把傀儡线程代码daemon=True删掉,或者改为False,结果如下
主线程执行完,但是程序并没有真正结束,等待一会儿子线程结果输出了,程序结束。
2、线程锁
2.1、为什么要锁
为什么要线程锁,只要有线程定义、线程开始、线程阻塞执行三个要点就够了吗?答案是肯定不够的
我们来看一个demo
该demo功能是一个多线程累加和一个多线程累减,
按照上面已经学到的知识可以很轻松写出下面代码
2.2、不加锁代码
import threadingn=0
def add(num):#累加的函数global nfor _ in range(num):n+=1def sub(num):#减的函数global nfor _ in range(num):n -= 1num = 10000000
th_add = threading.Thread(target=add,args=(num,))#定义线程,把num传参进去
th_sub = threading.Thread(target=sub,args=(num,))th_add.start()#启动线程
th_sub.start()th_add.join()#阻塞执行
th_sub.join()print("n=",n)
print("End")
简单分析一下,定义了一个多线程的加函数,又定义了一个多线程减函数,分别启动后阻塞等待程序执行完,不管输入的数字是多少,期待的结果总是为0才对,执行完毕后,结果如下:
n为啥不等于0,多换几个数字,依然不等于0
原因分析:
假设n=100的时候,首先加法线程取出100,然后进行加一操作等于101,但是这时候异步在执行,没有等把101结果返回给n的时候减法函数从内存中取出了n值为100,同样进行了减1操作,结果等于99,这时候99的结果和101结果同时赋值给n,所以这时候n的结果不是99就是101,但是不管结果是99或者101,这个时候n值已经错了(n值原来为100,加了一个数,然后又减了一个数,结果应该还是100才对)。
所以这时候就要用到线程锁来协调数据,在进行加法或者减法操作的时候希望n值是计算完成,写进去了,这样就不会乱了。
先在开头定义一把锁:lock = threading.Lock()。加锁有两种写法,可以用 lock.acquire()加锁,lock.release()解锁方法;或者直接with lock方法。
下面demo分别演示了两种加锁方式
2.3、加锁代码
import threading
lock = threading.Lock()#在开头定义一把锁
n=0
def add(num):#加的函数global nfor i in range(num):lock.acquire()#加锁方法一。相当于java 的 lock.lock()try:n+=1finally:lock.release()#解锁def sub(num):#减的函数global nfor _ in range(num):with lock:#加锁方法二n -= 1num = 10000000
th_add = threading.Thread(target=add,args=(num,))#定义线程,把num传参进去
th_sub = threading.Thread(target=sub,args=(num,))th_add.start()#启动线程
th_sub.start()th_add.join()#阻塞执行
th_sub.join()print("n=",n)
print("End")
不管num是多少,结果为0。符合预期结果
3、条件锁
有锁就够了吗,感觉还不够方便。因为如果我有一个缓冲区(缓冲区可以是个有限长度的列表,也类似于一个内存,内存容量是有限的,缓冲区的容量同样是指有限容量的空间),要在缓冲区内想利用多线程写进数据,同时也想从缓冲区获取数据,获取完数据就删除数据,释放缓冲区空间。缓冲区有大小,所以如果已经满了就写不进去,如果缓冲区没用数据,则获取失败。该问题就是生产者和消费者问题,或者也叫缓冲区问题。这时候就要用到条件锁
条件锁的定义方法:
has_data = threading.Condition(lock)#里面要传入一个锁,构成条件锁
has_loc= threading.Condition(lock)
一个lock可以有很多Condition,一个Condition只有一个lock。
Condition里面有个wait方法进行条件阻塞,配合if可以实现带条件的锁定和解锁。
阻塞的wait方法返回条件有两个,一是激活notify_all;二是timeout时间到了;
例如下面一个样例:1个lock,赋给了两个Condition,一个是C1,一个是C2;C1下面又有两个线程与其有关th1、th2;C2下面又有两个线程与其有关th3、th4、th5。如果这时候notify_all C1,则下面的th1和th2都活过来了。如果这时候notify_all C2,则th3、th4、th5都活过来了。
3.1、条件锁代码
下面看看代码
#程序功能:生产者消费者问题
import threadingclass Buffer:#定义缓冲区类def __init__(self,buffer_size:int):self.buffer_size = buffer_size#缓冲区大小self.buffer = []#用一个列表模拟缓冲区lock = threading.RLock()#RLock允许在lock中进行第二次调用acquire()进行锁定,而如果是普通lock,不允许在lock中套入lock的acquire(),这样造成锁死(简单的说:一个锁把带有钥匙的主人给锁住了)。所以一般的RLock比较常用,这种lock相当于java ReentrantLockself.has_lock = threading.Condition(lock)#条件锁定义,表示有空位self.has_data = threading.Condition(lock)#条件锁定义,表示有数据def put(self,data,blocking = True):#向buffer传入数据,生产者。blocking是判定有没有缓存空间写入数据#return True if the data is put into this buffer successfully,False otherwisewith self.has_lock:if blocking:while len(self.buffer)>=self.buffer_size:#条件锁的条件self.has_lock.wait()#阻塞has_lockelse:if len(self.buffer) >= self.buffer_size:return Falseself.buffer.append(data)self.has_data.notify_all()#激活has_data条件锁return Truedef get(self,blocking = True):#取数据(消费者)with self.has_data:if blocking:while len(self.buffer)==0:#取数据等待的条件是缓冲区没用数据self.has_data.wait()#阻塞has_dataelse:if len(self.buffer)==0:return Falseresult = self.buffer[0]del self.buffer[0]self.has_lock.notify_all()#删除了取出的数据,说明有空位了,这时候激活has_lockreturn resultif __name__ == '__main__':num = 20buffer= Buffer(5)def produce(n:int):#生产函数for i in range(num):data = "data_%d_%d"%(n,i)buffer.put(data)print(data,"is produced.")def resume():#消费函数for _ in range(num):data = buffer.get()print(data," is resume.")th0 = threading.Thread(target=produce,args=(0,))#定义一个多线程,调用生产的函数th1 = threading.Thread(target=produce,args=(1,))th2 = threading.Thread(target=resume)#定义一个多线程,调用消费的函数th3 = threading.Thread(target=resume)th0.start()th1.start()th2.start()th3.start()th0.join()th1.join()th2.join()th3.join()print("The test is End")
就这样吧,后面再在开新文章,增加几个多线程案例。
python多线程异步(一)相关推荐
- python 多线程 异步_python 多线程异步
最近做了个爬取代理的爬虫,使用了python的aysncio及concurrent.futures的ThreadPoolExecutor(线程池)技术,最终完成了多线程下的异步抓取,在此mark下,以 ...
- python多线程异步爬虫-Python异步爬虫试验[Celery,gevent,requests]
以往爬虫都是用自己写的一个爬虫框架,一群Workers去Master那领取任务后开始爬.进程数量等于处理器核心数,通过增开线程数提高爬取速度. 最近看了Celery,接口真是优美,挺想试验下异步模型来 ...
- python多线程异步 简单小栗子(包子大战)
#coding:utf-8 import threading import timedef producer():print u'tantianran: 等人来买包子...'event.wait() ...
- python多线程加锁异步处理装饰器
2019独角兽企业重金招聘Python工程师标准>>> 前言: 虽谈python多线程带有全局锁PIL,似乎对性能提升没什么意义,一般考虑多进程或者协程,但PIL没有被去掉还是应该有 ...
- Python多线程多进程、异步、异常处理等高级用法
文章目录 前言 多线程多进程 多线程 多进程 协程 总结 异步 基本概念 异步编程 asyncio aiohttp 异常 常见异常 异常处理 自定义异常 lambda表达式 lambda表达式用法 高 ...
- Python爬虫【四】爬取PC网页版“微博辟谣”账号内容(selenium多线程异步处理多页面)
专题系列导引 爬虫课题描述可见: Python爬虫[零]课题介绍 – 对"微博辟谣"账号的历史微博进行数据采集 课题解决方法: 微博移动版爬虫 Python爬虫[一]爬取移 ...
- python 多线程和协程结合_Python 异步编程,看这门课就够了~
我们常见的 Linux.Windows.Mac OS 操作系统,都是支持多进程的多核操作系统.所谓多进程,就是系统可以同时运行多个任务.例如我们的电脑上运行着 QQ.浏览器.音乐播放器.影音播放器等. ...
- Python|并发编程|爬虫|单线程|多线程|异步I/O|360图片|Selenium及JavaScript|Scrapy框架|BOM 和 DOM 操作简介|语言基础50课:学习(12)
文章目录 系列目录 原项目地址 第37课:并发编程在爬虫中的应用 单线程版本 多线程版本 异步I/O版本 总结 第38课:抓取网页动态内容 Selenium 介绍 使用Selenium 加载页面 查找 ...
- python多线程爬虫实例-Python多线程爬虫简单示例
python是支持多线程的,主要是通过thread和threading这两个模块来实现的.thread模块是比较底层的模块,threading模块是对thread做了一些包装的,可以更加方便的使用. ...
最新文章
- IAR的const,变量指定绝对地址,函数指定存取区域
- mysql limit 和 offset用法
- android 7.1 apk的systemuid和系统应用Setting相同导致开机找不到库的问题
- 无忧PHP企业网站内容管理系统源码v2.8 标准版
- 【Elasticsearch】Elasticsearch 最佳实践系列之分片恢复并发故障
- python中实现多路分支的最佳控制结构是_哪个选项是实现多路分支的最佳控制结构? (1.3分)_学小易找答案...
- iphone开机白苹果_「手机维修自学教程」苹果6PLUS的DFU模式故障维修技巧思路决定速度...
- 学计算机要6选3选什么学科,新高考选科6选3的学科及专业对应表查询系统
- 53. PHP 伪静态(2)
- 前端路由和后端路由(浅聊)
- 为什么MyBatis配置映射器只有四种
- 2018全国计算机二级c语言题库,全国计算机二级c语言题库试题及答案
- 两个卡巴斯基 6.0 官方简体中文版授权文件
- 西电2019计算机等级考试,西安电子科技大学2019《计算方法》期末考试试题
- python群发邮件
- 网站建设之帝国cms搭建小技巧详细搭建配置教程
- 双一次算法作业hhhhhhhhh
- element级联选择器城市3级联动三级联动json数据
- RK3399 GMAC驱动失败,打印如下log,DMA engine initialization failed 原因
- 确定电气间隙和爬电距离