Python爬虫之多线程
详情点我跳转
关注公众号“轻松学编程”了解更多。
多线程
在介绍Python中的线程之前,先明确一个问题,Python中的多线程是假的多线程!
为什么这么说,我们先明确一个概念,全局解释器锁(GIL)
一、什么是GIL
Python代码的执行由Python虚拟机(解释器)来控制,同时只有一个线程在执行。对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。
二、为什么要用GIL
为了线程间数据的一致性和状态同步的完整性,(例如:线程2需要线程1执行完成的结果,然而线程2又比线程1执行时间短,线程2执行完成,线程1仍然还在执行,这就是数据的同步性)
三、GIL的影响
只有一个线程在运行,无法使用多核。
在多线程环境中,Python虚拟机按照以下方式执行。
1.设置GIL。
2.切换到一个线程去执行。
3.运行。
4.把线程设置为睡眠状态。
5.解锁GIL。
6.再次重复以上步骤。
比方我有一个4核的CPU,那么这样一来,在单位时间内每个核只能跑一个线程,然后时间片轮转切换。
但是Python不一样,它不管你有几个核,单位时间多个核只能跑一个线程,然后时间片轮转。
执行一段时间后让出,多线程在Python中只能交替执行,10核也只能用到1个核
例如: cpu --30%
from threading import Thread
def loop():
while True:
print("亲爱的,我错了,我能吃饭了吗?")
if __name__ == '__main__':
for i in range(3):
t = Thread(target=loop)
t.start()
while True:
pass
而如果我们变成进程呢?cpu --100%
from multiprocessing import Process
def loop():
while True:
print("亲爱的,我错了,我能吃饭了吗?")
if __name__ == '__main__':
for i in range(3):
t = Process(target=loop)
t.start()
while True:
pass
四、多线程怎么使用多核
- 1、重写python编译器(官方cpython) 如使用:PyPy解释器
- 2、调用C语言的链接库
五、cpu密集型(计算密集型)、I/O密集型
- 计算密集型任务由于主要消耗CPU资源,代码运行效率至关重要,C语言编写
- IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成。99%的时间花费在IO上,脚本语言(如python)是首选,C语言最差。
六、创建多线程
#####1、使用_thread.start_new_thread开辟子线程
用这种方式创建的线程为【守护线程】(主线程死去“护卫”也随“主公”而去),主线程死掉,子线程也死掉(不管子线程是否执行完)。注意:python3以后已经放弃这种创建子线程的方式,所以在使用时可能会出错。
import _thread
import threading
import time
def doSth(arg):
# 拿到当前线程的名称和线程号id
threadName = threading.current_thread().getName()
tid = threading.current_thread().ident
for i in range(5):
print("%s *%d @%s,tid=%d" % (arg, i, threadName, tid))
time.sleep(2)
def simpleThread():
# 创建子线程,执行doSth
# 用这种方式创建的线程为【守护线程】
#主线程死去“护卫”也随“主公”而去
_thread.start_new_thread(doSth,("开启了子线程",))
mainThreadName = threading.current_thread().getName()
print(threading.current_thread())
for i in range(5):
print("我是主线程@%s" % (mainThreadName))
time.sleep(1)
# 阻塞主线程,以使【守护线程】能够执行完毕
while True:
pass
if __name__ == '__main__':
simpleThread()
#####2、 通过创建threading.Thread对象实现子线程
默认创建的不是守护进程,可以通过方法setDaemon(True)来修改。
import threading
import time
def doSth(arg):
# 拿到当前线程的名称和线程号id
threadName = threading.current_thread().getName()
tid = threading.current_thread().ident
for i in range(5):
print("%s *%d @%s,tid=%d" % (arg, i, threadName, tid))
time.sleep(2)
def threadingThread():
# 默认不是【守护线程】
# args=(,) 必须是元组
t = threading.Thread(target=doSth,args=('我是子线程',))
# t.setDaemon(True) # 设置为守护线程
# 设置主线程名称
t.setName('线程')
# 启动线程,调用run()方法
t.start()
# 等待子线程执行完
t.join()
# 获取线程名称
print(t.getName(),'执行完毕')
if __name__ == '__main__':
threadingThread()
3、通过继承threading.Thread类,进而创建对象实现子线程
覆写父类的run方法。
import threading
import time
def doSth(arg):
# 拿到当前线程的名称和线程号id
threadName = threading.current_thread().getName()
tid = threading.current_thread().ident
print("%s @%s,tid=%d" % (arg, threadName, tid))
time.sleep(2)
class MyThread(threading.Thread):
def __init__(self,name):
super().__init__()
# 覆盖了父类的name
self.name = name
# 覆写父类的run方法,
# run方法以内为【要跑在子线程内的业务逻辑】
#thread.start()会触发的业务逻辑
def run(self):
print(threading.current_thread().getName())
print(threading.current_thread().daemon)
# 如果为True就是守护线程,
#threading.current_thread().ident 线程id
doSth("线程id为%d"%threading.current_thread().ident)
if __name__ == '__main__':
for i in range(5):
mt = MyThread('线程%d'%i)
# 启动线程
mt.start()
#####4、几个重要的Adef importantAPI():
print(threading.currentThread()) # 返回当前的线程变量
# 创建五条子线程
t1 = threading.Thread(target=doSth, args=(“巡山”,))
t2 = threading.Thread(target=doSth, args=(“巡水”,))
t3 = threading.Thread(target=doSth, args=(“巡鸟”,))
t1.start() # 开启线程
t2.start()
t3.start()
print(t1.isAlive()) # 返回线程是否活动的
print(t2.isDaemon()) # 是否是守护线程
print(t3.getName()) # 返回线程名
t3.setName("巡鸟") # 设置线程名
print(t3.getName())
print(t3.ident) # 返回线程号
# 返回一个包含正在运行的线程的list
tlist = threading.enumerate()
print("当前活动线程:", tlist)
# 返回正在运行的线程数量(在数值上等于len(tlist))
count = threading.active_count()
print("当前活动线程有%d条" % (count))`
七、线程冲突
1、示例:
import threading
money = 0
def addMoney():
global money
for i in range(10000000):
money += 1
print(money)
if __name__ == '__main__':
# addMoney()
for i in range(2):
t = threading.Thread(target=addMoney)
t.start()
输出:
11769218
12363994
输出应该为:
10000000
20000000
原因:CPU分配的时间片不足以完成一百万次加法运算,因此结果还没有被保存到内存中就被其它线程所打断。
由于多个线程并发访问同一个变量而互相干扰,所以造成输出结果不对。
2、使用互斥锁解决冲突
互斥锁
状态:锁定/非锁定
创建锁: lock = threading.Lock()
成对出现:
if lock.acquire():
money +=1
lock.release()
使用with来管理
with lock:
money +=1
import threading
import time
money = 0
# 创建线程锁
lock = threading.Lock()
def addMoney():
global money
for i in range(10000000):
money += 1
print(money)
def addMoneyLock():
global money
if lock.acquire():
# -----下面的代码只有拿到lock对象才能执行-----
for i in range(10000000):
money += 1
# 释放线程锁,以使其它线程能够拿到并执行逻辑
lock.release()
# ----------------锁已被释放-----------------
print(money)
def addMoneyWithLock():
time.sleep(1)
global money
# 独占线程锁
with lock: # 阻塞直到拿到线程锁
# -----下面的代码只有拿到lock对象才能执行-----
for i in range(1000000):
money += 1
# 释放线程锁,以使其它线程能够拿到并执行逻辑
# ----------------锁已被释放-----------------
print(money)
# 5条线程同时访问money变量,导致结果不正确
def conflictDemo():
for i in range(5):
t = threading.Thread(target=addMoney)
t.start()
# 通过依次独占线程锁解决线程冲突
def handleConflictByLock():
# 并发5条线程
for i in range(5):
t = threading.Thread(target=addMoneyWithLock)
t.start()
if __name__ == '__main__':
time.clock()
# conflictDemo()
handleConflictByLock()
print(time.clock())
3、使用递归锁解决冲突
由于线程中可能会出现互相锁住对方线程需要的资源,造成死锁局面,所以使用递归锁,用于解决死锁的问题,可重复锁。
import threading
money = 0
# 创建线程锁
rlock = threading.RLock()
def addMoney():
global money
with rlock:
for i in range(10000000):
money += 1
print(money)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=addMoney)
t.start()
4、通过线程同步来解决冲突
使用t.join()函数阻塞:
import threading
import time
money = 0
def addMoney():
global money
for i in range(10000000):
money += 1
print(money)
# 通过线程同步(依次执行)解决线程冲突
def handleConflictBySync():
for i in range(5):
t = threading.Thread(target=addMoney)
t.start()
t.join() # 一直阻塞到t运行完毕
if __name__ == '__main__':
time.clock()
handleConflictBySync()
print(time.clock())
八、使用Semaphore调度线程:控制最大并发量
并行:多条一起运行
并发:伪并行,同一时间,启动了多个,轮循执行
import threading
# value 控制的线程数
import time
sem = threading.Semaphore(3)
'''
sem.acquire() # 加锁
sem.release()
'''
def doSth(arg):
with sem:
tname = threading.current_thread().getName()
print("%s正在执行【%s】" % (tname, arg))
time.sleep(1)
print("-----%s执行完毕!-----\n" % (tname))
time.sleep(0.1)
if __name__ == '__main__':
threadList = []
for i in range(10):
t = threading.Thread(target=doSth,args=(i,))
t.start()
threadList.append(t)
# 保证子线程正常结束
for t in threadList:
t.join()
九、生产消费者模型
通过threading.Condition实现线程通信
'''
生产消费模型
'''
import random
import threading
# 线程通信信物
condition = threading.Condition()
# 产品容器
pList = []
class Product():
'''
产品类
'''
def __init__(self,name):
self.name = name
def __str__(self):
return "%s个产品" %self.name
class Producer(threading.Thread):
'''
生产者
'''
def run(self):
while True:
# 生产产品
with condition:
p = Product(random.randint(100,1000))
print("生产了:",p)
# 存放到容器
pList.append(p)
# 通知消费者,谁wait()了就通知谁
condition.notify()
# 监听消费者通知,谁wait代表谁希望被notify
#(wait中会释放condition)
condition.wait()
# with走完,交出condition
# 此处condition已释放(condition.release())
class Consumer(threading.Thread):
'''
消费者
'''
def run(self):
while True:
# 拿到产品
with condition:
try:
p = pList.pop()
print("消费者消费了:",p)
# 通知生产者生产,谁with了相同condition
#且wait就通知谁
condition.notify()
# 等候生产者消息(wait中会释放condition)
condition.wait()
except:
print("没有产品")
# 此处condition已释放(condition.release())
if __name__ == '__main__':
p = Producer()
c = Consumer()
p.start()
c.start(
后记
【后记】为了让大家能够轻松学编程,我创建了一个公众号【轻松学编程】,里面有让你快速学会编程的文章,当然也有一些干货提高你的编程水平,也有一些编程项目适合做一些课程设计等课题。
也可加我微信【1257309054】,拉你进群,大家一起交流学习。
如果文章对您有帮助,请我喝杯咖啡吧!
公众号
关注我,我们一起成长~~
Python爬虫之多线程的更多相关文章
- python爬虫之多线程、多进程+代码示例
python爬虫之多线程.多进程 使用多进程.多线程编写爬虫的代码能有效的提高爬虫爬取目标网站的效率. 一.什么是进程和线程 引用廖雪峰的官方网站关于进程和线程的讲解: 进程:对于操作系统来说,一个任 ...
- Python爬虫之多线程下载豆瓣Top250电影图片
爬虫项目介绍 本次爬虫项目将爬取豆瓣Top250电影的图片,其网址为:https://movie.douban.com/top250, 具体页面如下图所示: 本次爬虫项目将分别不使用多线程和使 ...
- Python爬虫之多线程下载程序类电子书
近段时间,笔者发现一个神奇的网站:http://www.allitebooks.com/ ,该网站提供了大量免费的编程方面的电子书,是技术爱好者们的福音.其页面如下: 那么我们是否可以通过Py ...
- python爬虫之多线程、多进程、GIL锁
背景: 我们知道多线程要比多进程效率更高,因为线程存在于进程之内,打开一个进程的话,首先需要开辟内存空间,占用内存空间比线程大.这样想也不怪,比如一个进程用10MB,开10个进程就得100MB的内存空 ...
- 【新手必学】Python爬虫之多线程实战
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.作者:清风化煞_ 正文 新手注意:如果你学习遇到问题找不到人解答,可以点 ...
- Python爬虫进阶 | 多线程
一.简介 为了提高爬虫程序效率,由于python解释器GIL,导致同一进程中即使有多个线程,实际上也只会有一个线程在运行,但通过request.get发送请求获取响应时有阻塞,所以采用了多线程依然可以 ...
- 爬虫篇-python爬虫中多线程的使用
queue介绍 queue是python的标准库,俗称队列.可以直接import引用,在python2.x中,模块名为Queue.python3直接queue即可 在python中,多个线程之间的数据 ...
- Python爬虫练习(多线程,进程,协程抓取网页)
详情点我跳转 关注公众号"轻松学编程"了解更多. 一.多线程抓取网页 流程:a.设置种子url b.获取区域列表 c.循环区域列表 d.创建线程获取页面数据 e.启动线程 impo ...
- Python爬虫的N种姿势
问题的由来 前几天,在微信公众号(Python爬虫及算法)上有个人问了笔者一个问题,如何利用爬虫来实现如下的需求,需要爬取的网页如下(网址为:https://www.wikidata.org/w/ ...
随机推荐
- Go-missing return at end of function
where? Go程序中函数在执行的时候 why? 函数有返回参数,但是函数没有return关键字,报错 way? 添加return返回函数需要返回的参数
- Springer editorial manager上传latex文件
Springer的投稿系统editorial manager在初次投稿时只需要上传pdf文件,修改后要求上传Latex源文件.上传过程遇到好多问题,花了快两天才搞定,整理如下: 1. 主要上传的文件包 ...
- Leetcode-二分
69. x的平方根 https://leetcode-cn.com/problems/sqrtx/ 实现 int sqrt(int x) 函数. 计算并返回 x 的平方根,其中 x 是非负整数. 由于 ...
- 温故知新————c++ 多态
参考: 1. https://blog.csdn.net/weixin_42678507/article/details/89414998 (直接说明原理) 2 .https://www.cnblo ...
- excel——VlookUp函数的使用
VlookUp函数,查询两个表中的相同字段数据,并将需要引用的数据从B表填充到A表 1.打开A表,将需要查询的列选中 在需要引用的列输入 = 在上方,函数选择中选择VLOOKUP函数 Windows: ...
- 九、Python+Selenium模拟登录
研究QQ登录规则的话,得分析大量Javascript的加密解密,比较耗时间.自己也是练习很少,短时间成功不了.所以走了个捷径. Selenium是一个WEB自动化测试工具,它运行时会直接实例化出一个浏 ...
- MeteoInfoLab脚本示例:计算涡度、散度
用U/V分量数据计算涡度和散度,计算涡度的函数是hcurl,计算散度的函数是hdivg,参数都是U, V.脚本程序: f = addfile('D:/Temp/GrADS/model.ctl') u ...
- webpack5文档解析(上)
webpack5 声明:所有的文章demo都在我的仓库里 webpack5 起步 概念 webpack是用于编译JavaScript模块. 一个文件依赖另一个文件,包括静态资源(图片/css等),都会 ...
- centos7搭建docker环境
Docker简介 Docker是一种虚拟化技术,用来将你的应用程序及其依赖的环境一起打包成一个镜像发布,使得在任何地方都能获得相同的运行环境. Docker 是一个开源项目,诞生于 2013 年初,最 ...
- pytest文档58-随机执行测试用例(pytest-random-order)
前言 通常我们认为每个测试用例都是相互独立的,因此需要保证测试结果不依赖于测试顺序,以不同的顺序运行测试用例,可以得到相同的结果. pytest默认运行用例的顺序是按模块和用例命名的 ASCII 编码 ...