在上一篇文章中,我们了解了一些 python 高效爬虫的概念,
python3爬虫系列14之进程、单进程、多进程、线程、单线程、多线程、并行、并发、互斥锁、协程的白话解释,这一系列。
那么我们这一篇就开始了解多线程的使用介绍。
现在的 PC 都是多核的,使用多线程能充分利用 CPU 来提供程序的执行效率。
每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程。一个进程可包含多个线程,其中有且只有一个主线程。
在 python 中常用的多线程的模块有这么几个:
py2的时候,有个 thread 模块,被 python3 改名为 _thread 模块,但其实 _thread 也没什么人用。
其实 _thread 也没什么人用,因为 _thread 有的 threading 都有,_thread 没有的 threading 依然有,所以推荐学习threading 。
(自从我看了一篇白话的写作风格以后,博主觉得很有趣,后面就想尝试这样写了。因为一味的搬运概念解释的太多了。)
在这里介绍一下自己,博主是某工地的搬砖工。
【任务:博主臭弟弟每天工作得搬20块砖,每隔一秒钟搬一块。累死了~】
用代码实现如下:
#!/usr/bin/python3
#@Readme : 1.无线程举例
import time
def job_time(name, delay, counter):
while counter:
time.sleep(delay)
print("%s 开始搬第%d块砖 %s" % (name,counter, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
counter -= 1
if __name__ == '__main__':
job_time('鼠标',1,20)
搬砖开始:
搬砖好辛苦啊,昨天博主学习了线程的知识,于是知道了多线程。
可以使用多线程啊。
多个threading组成多线程——搬砖拉上几个小伙伴,一起搬,它不香嘛?
说干就干.
假设把朋友小明和小红拉来,让他们一起帮我搬砖。
也就是让小明和小红同时一人搬10块砖,这就省去一半的工作了。O(∩_∩)O哈哈哈~
【任务:让小明和小红共同搬10块砖,小红每搬1块砖休息 2 秒,小明每搬1块休息 1 秒,美滋滋~】
用代码实现如下:
#!/usr/bin/python3
#@Readme : 2.多线程做法
import threading
import time
# 创建一个线程类,然后继承 threading.Thread
class MyThread(threading.Thread):
def __init__(self,threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
# 定义run方法,调用了搬砖的方法-也就是我们要做的事情
def run(self):
print("开始线程:" + self.name)
job_time(self.name, self.counter, 10)
print("退出线程:" + self.name)
def job_time(threadName, delay, counter):
while counter:
time.sleep(delay)
print("%s 开始搬第%d块砖 %s" % (threadName,counter, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
counter -= 1
# 创建了两个线程
def Create_thread():
# 找了两个人来搬砖-让小明和小红同时一人搬 10 块砖.
thread1 = MyThread(1, "小明", 1) # 让小明搬一次砖休息1秒钟
thread2 = MyThread(2, "小红", 2) # 让小红搬一次砖休息2秒钟
# 开启新线程-就会去调用start方法-接着执行run方法
thread1.start()
thread2.start()
# 等待至线程中止-再终止主程序
thread1.join()
thread2.join()
print("退出主线程")
if __name__ == '__main__':
Create_thread()
1.首先我们呢创建了一个线程类class MyThread(threading.Thread):
2.然后继承 threading.Thread。
3.然后在这个线程类里面定义了一个 run 方法,
4.这个 run 方法去调用了搬砖的方法
其中我们创建了两个线程:
一个叫小明线程: thread1 = MyThread(1, "小明", 1) # 休息1秒
一个叫小红线程: thread2 = MyThread(2, "小红", 2) #休息2秒
当我们的线程调用 start 方法的时,它们就会去执行 run 方法,然后执行join方法,join 方法:是为了让线程执行完毕,再终止主程序。
thread1 = MyThread() # 这个地方,我们需要几个线程就再加几个,
比如
thread5 = MyThread(55, "张三", 2)
thread6 = MyThread(56, "李四", 5).....更多
但是!!!问题来了,搬砖完毕了,请吃饭花钱太多吧,一人请一顿划不来吧?
也就是
这样频繁的线程创建,销毁线程,是非常浪费资源的,所以我们应该把他们放到一个容器里面去统一管理。
而这个容器,称为——线程池
通过线程池就可以重复利用线程,这样就不会造成过多的浪费了。(一次性请搬砖的大伙吃饭,就省去好多Q了~~)
在 python 中可以使用 ThreadPoolExecutor 来实现线程池。
【比如,我们往池子里塞 20 个线程(就是请了20个朋友吃饭),然后在循环的时候每次拿一个线程(朋友)来搬砖。】
线程池用代码实现如下:
#!/usr/bin/python3
'''
#@Readme : 3.线程池的例子
在创建线程的时候,我们虽然可以需要几个就加几个,但是这样频繁的创建,
销毁线程,是非常的浪费资源。
所以提倡把他们放到池子里面去统一管理——这就是线程池。ThreadPoolExecutor
比如往池子里塞20个线程,然后在循环的时候每次拿一个线程来搬砖
'''
from concurrent.futures import ThreadPoolExecutor
import time
# 我们要做的事情
def job_time(name, delay, counter):
while counter:
time.sleep(delay)
print("%s 开始搬第%d块砖 %s" % (name, counter, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
counter -= 1
if __name__ == '__main__':
pool = ThreadPoolExecutor(20) # 创建线程池,包含20个线程
for i in range(1,21): # 循环拿一个线程来搬20块砖,这样就不会去重复的创建销毁线程了
pool.submit(job_time('鼠标'+str(i),1,3)) # 让线程搬一次砖休息3秒钟
在上诉代码中,我们使用ThreadPoolExecutor(20) 创建了一个线程池,里面包含20个线程。然后把这些线程,循环的方式提交去作业pool.submit,这样就不会去重复的创建销毁线程了。
使用线程池的这种方式,既节约了成本,又满足了工作。美滋滋~~~
当然了,请客也不一定只请吃饭嘛,也可以适当烤烤肉。
所以,创建线程池也不光光只有ThreadPoolExecutor类可以,我们还可以用一个叫做 Queue 的队列来创建线程池
Queue 就是队列嘛。(还记得大学数据结构吗?队列先进先出,栈先进后出。)
可以这样理解,
“队列”就是排队,队尾有人进入队列,另一头有人离开队列,即一端插入,一端删除;先排队的人先离开,即先进先出。
“栈”就是客栈,只能从门进出,即只在一端进行插入删除操作;进去了的人要想出来,就得门口的先出去,即先进后出。
速度不同:
栈只能从头部取数据,也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性。
队列则不同,它基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多。
咳咳,貌似跑偏了~~~
说回咱们的队列。
python在使用队列的时,最常用的方法就是 put (塞)和 get (取)了。
【比如,现在我创建一个长度为 10 的队列,紧接着根据队列的长度创建线程,然后每个线程都让它们处于守护状态(也就是需要的时候,马上执行)】
Queue线程池代码如下:
import threading
from queue import Queue
import time
# 4.使用Queue队列来创建线程池
# 最常用的方法是put和get
class CustomThread(threading.Thread):
# 初始化属性
def __init__(self, queue,number):
threading.Thread.__init__(self)
self.__queue = queue
self.number = number # 线程编号
# 要执行的话就需要去队列里面取了get
def run(self):
print('启动%d号线程' % self.number)
while True:
q_method = self.__queue.get()
q_method()
self.__queue.task_done()
# 创建线程池-Queue队列
def queue_pool():
queue = Queue(10) # 创建一个长度为10的队列
for i in range(queue.maxsize): # 根据队列的长度创建了线程
t = CustomThread(queue,i+1) # 初始化
t.setDaemon(True) # 每个线程都设置为守护状态=即需要时马上执行
t.start() # 启动线程
for i in range(20):
queue.put(job) # put方法=把我们想做的事情往队列里面塞
queue.join()
# 想做的事情
def job():
print(" 开始搬砖 %s" % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
if __name__ == '__main__':
queue_pool()
运行一下:
我们创建一个长度为 6 的队列——任务箱子
接着根据队列的长度创建了线程——搬砖人员
每个线程都让它们处于守护状态——工地等待
也就是需要的时候马上执行——需要时就顶上
接着我们就可以用 put 方法,把我们想做的事情往队列里面塞——想搬砖,存放搬砖任务。
比如我们想要搬砖了,要执行的话就需要去队列里面 get 取得搬砖任务,开搬。
好了,到这里总结一下,我们今天主要是学习的就是:
计算时间的方法:
start_time = time.time()
print("danxianc time is: {}".format(time.time() - start_time))
或者:
start = time.time()
main() # 要调用的程序
end = time.time()
spendtime = end - start
print("用时 " + str(spendtime) + "秒")
可以用来计算你的爬虫程序,爬取花了多少时间。
下一篇,用多线程来爬取网站咯。喜欢您就点个赞吧~