主题
正常情况下,程序的运行按顺序执行,但是涉及某些操作,等待结果完成却是非常耗时的操作,比如爬虫进行IO操作等,当涉及的量较大的时候,同步执行的程序十分的耗时,为了使得支持并发操作,缩减程序运行的时间,提高效率。多线程技术就是为了实现这样的功能。
多线程编程中涉及到许多概念:
- 并发式编程
- 多线程和多进程
- 线程安全
- 线程的声明周期
- 线程的类型
- 创建线程
- 线程间的通信
- 事件通知
- 锁和可重入锁
- 线程挂起
实战型演示多线程编程,并列出我认为写的好的参考文献,供大家参考。
0.
全文思路
主要涉及的python模块是:threading
1.
threading
- 函数
- 类包装线程对象:实现run方法
- Queue:实现数据间的通信,生产者和消费者模式
# 1
创建新线程:threading.Thread(func, args=(,))
启动线程活动:start()
等待至线程终止:join()
# 2
生产者-消费者模型
Queue.put()
Queue.get()
# 3
互斥锁同步:数据共享;当多个线程都修改某一个共享数据的时候,需要进行同步控制。
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire([timeout])
# 释放
mutex.release()
# 4
死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
# 5
可重入锁:一个线程“迭代”请求同一个资源
2.
目标网站:
目标:获取一年内的停复牌信息
目标网址:http://www.cninfo.com.cn/cninfo-new/memo-2?queryDate=2016-11-17&queryType=queryType1
- 网址和日期相关
- 周末无数据
获取11月份有效url的方法:
不知道为啥我写的这么复杂又是回调函数,又是各种函数调用。
def crawlurl(year=2016, month=11, day=None, queue=None, *, callback, lastcall):
days = [i for i in range(1, 31)]
for day in days:
if callback(year, month, day=day):
data = datetime(year, month, day)
URL = lastcall(data)
queue.put(("url", URL))
# 判断日期是否有效,周末和未来网站无数据
def is_valid_day(year=2016, month=11, day=None):
data = datetime(year=year, month=month, day=day)
if data.weekday() in range(0, 5) and data <= datetime.today():
return True
# 构造url方法
def geturl(data):
url = "http://www.cninfo.com.cn/cninfo-new/memo-2?queryDate=%s&queryType=queryType1"
return url % changedatetime(data)
# 将日期格式化成2016-11-17形式
def changedatetime(date):
return date.strftime('%Y-%m-%d')
A = Queue()
allurl = crawlurl(2016,11, queue= A, callback=is_valid_day, lastcall=geturl)
print(A.qsize()) # 截止17号,网站存在13个有效日期:去除5,6,12,13周末日
A.put(("None", -1))
while True:
_, url = A.get()
if url == -1:
break
print(url)
一、实例化Thread类来使用多线程
Thread(target=func, args=())
# url是上文获取的url, queue 用来将IO操作抓取的网页信息存入队列中
def download(url, queue):
headers = {"Host": "www.cninfo.com.cn",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36"}
try:
resp = requests.get(url, headers=headers)
if resp.status_code == 200:
queue.put(("response", resp.text))
except:
print("Connection wrong")
# 这里整体的思路是:
# 1. 将获取的url存放在url_queue队列中
# 2. 将url_queue队列中的url 传入下载函数中
# 3. 将获取的网页信息数据存入:response_queue队列中
url_queue = Queue()
response_queue = Queue()
i = crawlurl(2016,11, queue= url_queue, callback=is_valid_day, lastcall=geturl)
print(url_queue.qsize()) # 13
url_queue.put(("None", -1)) # 插入标志信息好让函数返回
threads = []
while True:
_, url = url_queue.get()
if url == -1:
break
t = Thread(target=download, args=(url, response_queue))
threads.append(t)
t.start() # 启动线程
for j in threads:
j.join() # 线程挂起,主线程会等待子线程全部完成才继续运行
print(response_queue.qsize()) # 13
response_queue.put(("None", -1))
while True:
_, response = response_queue.get()
if response == -1:
break
print(len(response)) # 查看是否真的获取网页信息
#33087
#36111
#39310
#43046
#36163
#40661
#42014
#48080
#52088
#34507
#40294
#28014
#44018
二、继承Thread类,重写run方法
class Download(Thread):
def __init__(self, url, queue):
super().__init__()
self.url = url
self.queue = queue
def run(self):
response = self.download()
self.queue.put(("response", response))
def download(self):
headers = {"Host": "www.cninfo.com.cn",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36"}
try:
resp = requests.get(self.url, headers=headers)
if resp.status_code == 200:
return resp.text
except:
print("Connection wrong")
# 获取url
url_queue = Queue()
response_queue = Queue()
i = crawlurl(2016,11, queue= url_queue, callback=is_valid_day, lastcall=geturl)
print(url_queue.qsize())
url_queue.put(("None", -1))
threads2 = []
response_queue2 = Queue()
while True:
_, url = url_queue.get()
if url == -1:
break
t2 = Download(url, response_queue2)
threads2.append(t2)
for i in threads2:
i.start()
for j in threads2:
j.join()
print(response_queue2.qsize()) # 13
response_queue2.put(("None", -1))
while True:
_, response = response_queue2.get()
if response == -1:
break
print(len(response))
#33087
#34507
#28014
#44018
#48080
#36111
#43046
#36163
#40294
#40661
#39310
#52088
#42014
结果一致,执行顺序不同,多线程并发操作执行顺序具有随机性
3.
参考文献: