一. 如何使用多线程
实际案例
https://intrinio.com/tutorial/web_api
我们通过上述网站提供的API获取了股市信息的CSV数据,
现在要下载大量CSV数据文件, 并将其转化为xml文件
如何使用线程来提高下载并处理的效率?
解决方案
- 使用标准库 threading.Thread 创建线程,在每一个线程中下载并转换一只股票数据
import requests
import base64
from io import StringIO
import csv
from xml.etree.ElementTree import ElementTree, Element, SubElement
USERNAME = b'7f304a2df40829cd4f1b17d10cda0304'
PASSWORD = b'aff978c42479491f9541ace709081b99'
def download_csv(page_number):
print('download csv data [page=%s]' % page_number)
url = "http://api.intrinio.com/prices.csv?ticker=AAPL&hide_paging=true&page_size=200&page_number=%s" % page_number
auth = b'Basic ' + base64.b64encode(b'%s:%s' % (USERNAME, PASSWORD))
headers = {'Authorization' : auth}
response = requests.get(url, headers=headers)
if response.ok:
return StringIO(response.text)
def csv_to_xml(csv_file, xml_path):
print('Convert csv data to %s' % xml_path)
reader = csv.reader(csv_file)
headers = next(reader)
root = Element('Data')
root.text = '\n\t'
root.tail = '\n'
for row in reader:
book = SubElement(root, 'Row')
book.text = '\n\t\t'
book.tail = '\n\t'
for tag, text in zip(headers, row):
e = SubElement(book, tag)
e.text = text
e.tail = '\n\t\t'
e.tail = '\n\t'
ElementTree(root).write(xml_path, encoding='utf8')
def download_and_save(page_number, xml_path):
# IO
csv_file = None
while not csv_file:
csv_file = download_csv(page_number)
# CPU
csv_to_xml(csv_file, 'data%s.xml' % page_number)
from threading import Thread
class MyThread(Thread):
def __init__(self, page_number, xml_path):
super().__init__()
self.page_number = page_number
self.xml_path = xml_path
def run(self):
download_and_save(self.page_number, self.xml_path)
if __name__ == '__main__':
import time
t0 = time.time()
thread_list = []
for i in range(1, 6):
t = MyThread(i, 'data%s.xml' % i)
# t = Thread(target=download_and_save, args=(1, 'data%s.xml' % i))
t.start()
thread_list.append(t)
for t in thread_list:
t.join() # 主线程 join等待 子线程完成
print(time.time() - t0)
print('main thread end.')
二. 如何线程中通信
实际案例
在上一节中, 我们从 intrinio.com 下载多只股票的csv数据, 并将其转换为 xml 文件
在python中由于全局解释器锁 (GIL)的存在, 多线程CPU密集型操作并不能提高执行效率, 我们修改程序架构:
1. 使用多个 DownloadThread 线程进行下载(I/O)
2. 使用多个 ConvertThread 线程进行转换(CPU)
3. 下载线程把下载数据安全地传递给转换线程
GIL
- 在每个进程中, 存在一把GIL, 该进程中的线程间共享GIL
- 多线程进行时, 只有有GIL的那个线程能运行
- 通过线程间快速 传递GIL, 达到表象上的多线程, 其实同一时间只有一个线程在工作
解决方案
-
使用标准库中的 queue.Queue, 它是一个线程安全的队列
- Download 线程把下载数据放入队列
- Convert 线程从队列里提取数据
import requests
import base64
from io import StringIO
import csv
from xml.etree.ElementTree import ElementTree, Element, SubElement
from threading import Thread
USERNAME = b'7f304a2df40829cd4f1b17d10cda0304'
PASSWORD = b'aff978c42479491f9541ace709081b99'
# class MyThread(Thread):
# def __init__(self, page_number, xml_path):
# super().__init__()
# self.page_number = pa