7. 多线程并发相关问题和解决技巧

这篇博客探讨了多线程并发编程的各种问题及解决策略。内容包括使用`threading.Thread`创建线程下载股票数据,理解GIL概念及其在多线程中的影响,利用`queue.Queue`实现线程安全通信,运用`Threading.Event`进行线程间事件通知,使用`threading.local`管理线程本地数据,以及如何操作线程池和应用在web监控服务器上。同时,还涉及了多进程的使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 如何使用多线程

实际案例

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, 它是一个线程安全的队列

  1. Download 线程把下载数据放入队列
  2. 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值