38. 使用多線程

例如,我們通過 https://intrinio.com/tutorial/web_api 這個網址提供的api獲取股市信息的csv數據,現在要下載大量csv數據文件,並將其轉換爲xml文件。

要求:使用多線程來提高下載並處理的效率。

解決方案:

使用標準庫threading.Thread類創建多個線程,在每個線程中下載並轉換一隻csv數據。


  • 對於threading.Thread類:
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

調用該類時,必需帶有關鍵字參數。參數如下:

group應該爲None;爲了日後擴展ThreadGroup類實現而保留。

target是用於run()方法調用的可調用對象。默認是None,表示不需要調用任何方法。

name是線程名稱。默認情況下,由"Thread-N" 格式構成一個唯一的名稱,其中N是小的十進制數。

args是用於調用目標函數的參數元組。默認是()。

kwargs是用於調用目標函數的關鍵字參數字典。默認是{}。

如果daemon不是None,線程將被顯式的設置爲守護模式,不管該線程是否是守護模式。如果是None(默認值),線程將繼承當前線程的守護模式屬性。

threading.Thread類有以下方法:

start()

開始線程活動。

它在一個線程裏最多隻能被調用一次。它安排對象的run()方法在一個獨立的控制進程中調用。如果同一個線程對象中調用這個方法的次數大於一次,會拋出RuntimeError。

run()

代表線程活動的方法。

你可以在子類型裏重載這個方法。 標準的run()方法會對作爲target參數傳遞給該對象構造器的可調用對象(如果存在)發起調用,並附帶從args和kwargs參數分別獲取的位置和關鍵字參數。

join(timeout=None)

等待,直到線程終結。這會阻塞調用這個方法的線程,直到被調用join()的線程終結,不管是正常終結還是拋出未處理異常,或者直到發生超時,超時選項是可選的。

當timeout參數存在而且不是None時,它應該是一個用於指定操作超時的以秒爲單位的浮點數(或者分數)。因爲join()總是返回None,所以你一定要在join()後調用is_alive()才能判斷是否發生超時,如果線程仍然存貨,則join()超時。

當timeout參數不存在或者是None ,這個操作會阻塞直到線程終結。一個線程可以被join()很多次。

如果嘗試加入當前線程會導致死鎖,join()會引起RuntimeError異常。如果嘗試join()一個尚未開始的線程,也會拋出相同的異常。

  • 方案示例:
import requests
import base64
import csv
import time
from io import StringIO
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 = "https://api.intrinio.com/price.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'
    book.tail = '\n'

    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)

if __name__ == '__main__':
    t0 = time.time()
    for i in range(1, 6):
        download_and_save(i, 'data%s.xml' % i)
    print(time.time() - t0)
    print('main thread end.')

改進:使用threading.Thread類創建多個線程,同時下載轉換。

import requests
import base64
import csv
import time
from io import StringIO
from xml.etree.ElementTree import ElementTree, Element, SubElement
from threading import Thread

USERNAME = b'7f304a2df40829cd4f1b17d10cda0304'
PASSWORD = b'aff978c42479491f9541ace709081b99'

def download_csv(page_number):
    print('download csv data [page=%s]' % page_number)
    url = "https://api.intrinio.com/price.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'
    book.tail = '\n'

    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)


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()方法
        download_and_save(self.page_number, self.xml_path)

if __name__ == '__main__':
    t0 = time.time()
    thread_list = []
    for i in range(1, 6):
        t = MyThread(i, 'data%s.xml' % i)
        t.start()               #啓動線程
        thread_list.append(t)

    for t in thread_list:
        t.join()                #阻塞線程,主線程等待所有子線程結束

    print(time.time() - t0)
    print('main thread end.')

上面url已失效,無法看到實際耗時效果,但通過多個線程可以節約IO時間,Python中的多線程不適合處理CPU密集型的任務。


發佈了225 篇原創文章 · 獲贊 227 · 訪問量 38萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章