Queue模块提供了一个适用于多线程编程的先进先出数据结构,可以用来安全的传递多线程信息。
它本身就是线程安全的,使用put和get来处理数据,不会产生对一个数据同时读写的问题,所以是安全的。
也就是说在put或者get的时候,就有锁在里面的,也就不必自己繁琐的进行lock.acquire()和 lock.release来释放锁,
或者Rlock.acquire()和Rlock.release进行上锁解锁来保护数据,而且加锁会消耗资源,因为加锁或者释放锁的过程中都会消耗资源 。关于锁的详情:https://blog.csdn.net/weixin_43097301/article/details/84400264
Queue中的部分源码展示
class Queue:
'''Create a queue object with a given maximum size.
If maxsize is <= 0, the queue size is infinite.
'''
def __init__(self, maxsize=0):
self.maxsize = maxsize
self._init(maxsize)
# mutex must be held whenever the queue is mutating. All methods
# that acquire mutex must release it before returning. mutex
# is shared between the three conditions, so acquiring and
# releasing the conditions also acquires and releases mutex.
self.mutex = threading.Lock()
self._init(maxsize):使用了python自带的双端队列deque,来存储元素。
self.mutex互斥锁:任何获取队列的状态(empty(),qsize()等),
或者修改队列的内容的操作(get,put等)都必须持有该互斥锁。共有两种操作require获取锁,release释放锁。
同时该互斥锁被三个共享变量同时享有,即操作conditiond时的require和release操作也就是操作了该互斥锁。
self.not_full条件变量:当队列中有元素添加后,会通知notify其他等待添加元素的线程,唤醒等待require互斥锁,
或者有线程从队列中取出一个元素后,通知其它线程唤醒以等待require互斥锁。
self.not empty条件变量:线程添加数据到队列中后,会调用self.not_empty.notify()通知其它线程,
唤醒等待require互斥锁后,读取队列。
self.all_tasks_done条件变量:消费者线程从队列中get到任务后,任务处理完成,
当所有的队列中的任务处理完成后,会使调用queue.join()的线程返回,表示队列中任务以处理完毕
说明:
最后一个例子演示了生产者-消费者模型这个场景。在这个场景下,商品或服务的生产者 生产商品,然后将其放到类似队列的数据结构中。生产商品的时间是不确定的,同样消费者 消费生产者生产的商品的时间也是不确定的。
代码示例:
# 两个队列,一个生产者不断的去队列里面存放任务,一个消费者,不断的取队列里面消费任务
import time #时间库
import random #随机数库
import queue #线程安全的队列
import threading #多线程
def product_task_to_queue(queue):
'''
生产者:
不断的像队列中去加入任务,每隔1,3秒加入一次
:return:
'''
task_num = 1
while True:
print('product task', str(task_num),'task_size',queue.qsize())
queue.put(str(task_num)) #向队列添加数据
sleep_time = random.randint(1, 2) #随机1-2秒的时间
time.sleep(sleep_time) #随机休息
task_num += 1 #自我加一,记录数据
def consume_task_from_queue(queue):
"""
消费者:
不断的向队列中获取任务,然后消费任务,每隔2-4秒消费一次
:param queue:
:return:
"""
while True: #循环进行拿取任务
task_num = queue.get()
sleep_time = random.randint(3, 4)
time.sleep(sleep_time) #随机进行睡眠3-4秒
consume_task(task_num,queue) #将任务交付给处理函数进行处理
def consume_task(task_num,queue):
"""
消费者处理任务的函数
:param task_num:
:return:
"""
print('consume task num:', task_num,'task_size',queue.qsize())#打印数据
def loop():
"""
启动函数
:param task_num:
:return:
"""
que = queue.Queue(maxsize=10) #添加queue队列,限制任务上限为10
t1 = threading.Thread(target=product_task_to_queue,args=(que,)) #设置线程1
t2 = threading.Thread(target=consume_task_from_queue, args=(que,))#设置线程2
t1.start() #启动线程1
t2.start() #启动线程2
t1.join() #阻塞主线程
t2.join() #阻塞主线程
loop()
import requests # 页面请求
from bs4 import BeautifulSoup # 网页解析
from queue import Queue # 安全队列
from threading import Thread # 多线程
def get_html_doc(url): # 根据指定的url获取html文档
res = requests.get(url) # 向url链接发送get请求
print(res.content) # 打印拿到的网页内容
return res.content.decode("utf8") # 返回内容并用utf8解码
def get_detail(que):
while True:
url = que.get()
Thread(target=get_html_doc, args=(url,))
def parse_index(url, que, index_urls_list):
# 解析列表页面
html_doc = get_html_doc(url) # 拿到返回的网页内容
data = BeautifulSoup(html_doc) # 解析网页内容
detail_urls = data.select('[class=post-thumb] a') # 把index里面的url取出来再取下面的url
# data.select调用css选择器 选择出来是dict
for i in detail_urls:
que.put(i) # 获取细节的url,把细节的url添加到que队列中
index_urls = data.select('a[class=page-numbers]') # 取出所有其他index页面的翻页url 去解析其他的url
for i in index_urls:
if i not in index_urls_list: # 保证url不重复,
index_urls_list.append(i) # 添加到list里面,表示我已经处理了这个数据
url = i['href'] # 获取详细url
Thread(target=parse_index, args=(url, que)) # 多线程处理,启动函数parse_index,并传参
def start(url, que, index_urls_list): # 开启
parse_index(url, que, index_urls_list)
if __name__ == "__main__":
url = "http://blog.jobbole.com/category/it-tech/" # 爬取的网页地址
que = Queue() # 建立安全队列que
index_urls_list = []
start(url, que, index_urls_list) # 调用开启函数
queue的方式进行同步:
import requests
from bs4 import BeautifulSoup
from queue import Queue
from threading import Thread
import time
def get_html_doc(url): # 根据指定的url获取html文档
res = requests.get(url) #请求网页
print(res.content) #打印信息bytes(b“内容”)
return res.content.decode("utf8") #返回页面信息并utf8解码
def get_detail(detail_urls_queue): #循环处理拿到任务,并建个任务url传给函数获取页面
while True:
url = detail_urls_queue.get(1,timeout=2) #到安全队列中取值,每次取值间隔2s
print('consumer--->',url)
get_html_doc(url) #传参url给函数进行获取页面
def parse_index(url, detail_urls_queue): # 解析列表页面
html_doc = get_html_doc(url) #获取解码后的页面信息
data = BeautifulSoup(html_doc) #解析所得页面信息
# 把index里面的url取出来再取下面的url
# data.select调用css选择器 选择出来是dict
detail_urls = data.select('[class=post-thumb] a')
# 获取细节的url,把细节的url交给其他线程处理
for i in detail_urls:
url = i['href'] #字典取值,key value键值对取值 url
print('productor------>',url)
detail_urls_queue.put(url) #添加到安全队列,即添加任务
if __name__ == "__main__":
url = "http://blog.jobbole.com/category/it-tech/"
# 详细页面的url
detail_urls = Queue(100)
# 列表url 防止重复
# index_urls_list = [] #省略建一个列表用来去重复url
t1 = Thread(target=parse_index,args=(url,detail_urls)) #新建线程1,启动parse_index
t2 = Thread(target=get_detail, args=(detail_urls,)) #新建线程2,启动get_detail
t1.start()
t2.start()
# parse_index(url, detail_urls, index_urls_list)
t1.join()
t2.join()
print('down')
完善的多线程获取页面方式
import requests
from bs4 import BeautifulSoup
from queue import Queue
from threading import Thread,RLock
def get_html_doc(url):
# 根据指定的url获取html文档
res = requests.get(url)
# print(res.content)
return res.content.decode("utf8")
def get_detail(detail_urls_queue):
while True:
url = detail_urls_queue.get(1, timeout=2)
print('consumer--->', url)
get_html_doc(url)
def parse_index(detail_urls_queue, index_urls_queue,index_urls_queue_record):
while True:
url = index_urls_queue.get(1) #请求页面信息
print('get_index_url--->', url)
# 解析列表页面
html_doc = get_html_doc(url)
data = BeautifulSoup(html_doc) #解析页面
# 把index里面的url取出来再取下面的url
# data.select调用css选择器 选择出来是dict
detail_urls = data.select('[class=post-thumb] a')
# 获取细节的url,把细节的url交给其他线程处理
for i in detail_urls:
url = i['href']
print('productor------>', url)
detail_urls_queue.put(url)
# 取出所有其他index页面的翻页url 去解析其他的url
index_urls = data.select('a[class=page-numbers]')
for i in index_urls:
if i in index_urls_queue_record: #去重复操作
continue
url = i['href']
index_urls_queue.put(url) #添加入安全队列中
#在添加的时候,不要让gil去释放锁
locks.acquire() #上锁
index_urls_queue_record.append(url)
locks.release() #释放锁
print('put_index_url--->', url)
# 去重 使用redis数据库
if __name__ == "__main__":
url = "http://blog.jobbole.com/category/it-tech/"
# 详细页面的url
detail_urls = Queue(40)
index_urls_queue = Queue(5)
index_urls_queue.put(url)
#记录重复url
index_urls_queue_record = [url]
locks = RLock() #设置Rlock锁,防止多线程数据存储读取错误
# 列表url 防止重复
# index_urls_list = [] #也可以添加一个list用来去重,这里没有进行演示
t1 = Thread(target=parse_index, args=(detail_urls, index_urls_queue,index_urls_queue_record,locks))
t2 = Thread(target=get_detail, args=(detail_urls,))
t1.start()
t2.start()
t1.join()
t2.join()
print('down')
给出一个任务,然后交给线程池完成,线程池可以设置最大线程的数量,所以他会一次执行三个
from concurrent.futures import ThreadPoolExecutor
import time
#简单的线程池使用
def consume(num):
time.sleep(2)
print('consuming',num)
pools = ThreadPoolExecutor(3) #设置线程数3
num = 1
while True:
time.sleep(0.5)
pools.submit(consume,(num)) #启动线程池,运行函数consume
num += 1
说明:
基本使用方式:
concurrent.futures
库来实现多线程库
from concurrent.futures import ThreadPoolExecutor
import time
# 1.并发
# 2.获取线程的返回值 当一个线程完成的时候,主线程能够知道
# 3.让多线程和多进程编程接口一致
def get_html(sleep_time):
time.sleep(sleep_time)
print("get page {} success".format(sleep_time))
return sleep_time
executor = ThreadPoolExecutor(max_workers=2)
task1 = executor.submit(get_html,(3)) #通过sumbit提交到线程池中,间隔三秒
task2 = executor.submit(get_html,(2)) #间隔2秒
task3 = executor.submit(get_html,(2)) #间隔2秒
print(task3.cancel())
#print(task1.done()) # done 用于判断线程是否完成,完成返回True,否则返回False
print(task1.result())# result会阻塞 等待任务完成,获取返回结果
print(task2.result())
as_completed 方法
使用 as_completed方法来获取所有完成的线程,并获取返回值
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
import random
from functools import partial
def get_html(sleep_time,num):
time.sleep(sleep_time)
# print("get page {} success".format(sleep_time))
return num
executor = ThreadPoolExecutor(max_workers=2) #线程池上限任务两个
tasks = list()
for i in range(10):
sleep_time = random.randint(2, 5) #睡眠时间随机2-5秒
#把右边函数看成一个整体
tasks.append(executor.submit(partial(get_html,sleep_time), (i)))
# 通过sumbit提交到线程池中,进行一个添加get_html,
for i in as_completed(tasks):
data = i.result() #阻塞 等待完成的函数
print('num {} success'.format(data))
wait 阻塞主线程
wait可以等待tasks的某个任务或者所有任务完成之后再执行其他的(阻塞),
他有三种可选方式,默认是等待所有tasks完成
FIRST_COMPLETED = 'FIRST_COMPLETED' #一个完成
FIRST_EXCEPTION = 'FIRST_EXCEPTION' #一个异常
ALL_COMPLETED = 'ALL_COMPLETED' #所有完成
from concurrent.futures import ThreadPoolExecutor, as_completed, wait
import time
import random
def get_html(sleep_time):
time.sleep(sleep_time)
# print("get page {} success".format(sleep_time))
return sleep_time
executor = ThreadPoolExecutor(max_workers=2)
# 通过sumbit提交到线程池中
tasks = list()
for i in range(10):
sleep_time = random.randint(2, 5)
tasks.append(executor.submit(get_html, (sleep_time)))
#添加get_html(sleep_time)函数的返回值sleep_time
wait(tasks, return_when='FIRST_COMPLETED') #阻塞等待任务完成
for i in as_completed(tasks):
data = i.result()
print('num {} success'.format(data))
print('12312312')
示例:
通过上面的线程池,我们就可以把获取详细页面的任务交给线程池去获取
import requests
from bs4 import BeautifulSoup
from queue import Queue
from threading import Thread
from concurrent.futures import ThreadPoolExecutor
def get_html_doc(url): # 根据指定的url获取html文档
res = requests.get(url)
print('ex--->url',url)
return res.content.decode("utf8")
def get_detail(detail_urls_queue,pools): #从队列中拿取url任务,并进行传参进行页面请求
while True:
url = detail_urls_queue.get(1,timeout=2)
# print('consumer--->',url)
pools.submit(get_html_doc,(url))
def parse_index(detail_urls_queue,index_urls_queue):
#解析页面,并找出标签,取出标签中的url,最后添加到安全队列任务中
while True:
url = index_urls_queue.get(1)
# print('get_index_url--->',url)
# 解析列表页面
html_doc = get_html_doc(url)
data = BeautifulSoup(html_doc)
# 把index里面的url取出来再取下面的url
# data.select调用css选择器 选择出来是dict
detail_urls = data.select('[class=post-thumb] a')
# 获取细节的url,把细节的url交给其他线程处理
for i in detail_urls:
url = i['href'] #字典取值,或得详细url
# print('productor------>',url)
detail_urls_queue.put(url) #添加到安全队列任务中
# 取出所有其他index页面的翻页url 去解析其他的url
index_urls = data.select('a[class=page-numbers]')
for i in index_urls:
url = i['href'] #字典取值,获取详细url
index_urls_queue.put(url) #添加到安全队列,即添加任务
#去重 使用redis数据库
# print('put_index_url--->', url) #测试
if __name__ == "__main__":
url = "http://blog.jobbole.com/category/it-tech/"
# 详细页面的url
detail_urls = Queue()
index_urls_queue = Queue() #建立安全队列
index_urls_queue.put(url) #添加安全队列
# 列表url 防止重复
# index_urls_list = []
executor = ThreadPoolExecutor(max_workers=10)
t1 = Thread(target=parse_index,args=(detail_urls,index_urls_queue)) #设置线程
t2 = Thread(target=get_detail, args=(detail_urls,executor)) #设置线程
t1.start() #启动线程
t2.start() #启动线程
t1.join() #阻塞主线程
t2.join() #阻塞主线程
print('down')