python线程的使用模式

简介: 为了解决阻塞(如I/O)问题,我们需要对程序进行并发设计。 本文将通过将线程和队列 结合在一起,轻松地在 Python 中完成线程编程,创建一些简单但有效的线程使用模式。   一、使用线程 先看一个线程不多的例子,不存在阻塞,很简单: import threading import datetime class MyThread(threading.

为了解决阻塞(如I/O)问题,我们需要对程序进行并发设计。

本文将通过将线程和队列 结合在一起,轻松地在 Python 中完成线程编程,创建一些简单但有效的线程使用模式。

 

一、使用线程

先看一个线程不多的例子,不存在阻塞,很简单:

import threading
import datetime

class MyThread(threading.Thread):
    def run(self):
        now = datetime.datetime.now()
        print("{} says Hello World at time: {}".format(self.getName(), now))
        
for i in range(2):
    t = MyThread()
    t.start()

    代码解读

    1. 两个线程都输出了 Hello World 语句,并都带有日期戳。

    2. 两个导入语句:一个导入了日期时间模块,另一个导入线程模块。

    3. 类 MyThread 继承自 threading.Thread,也正因为如此,您需要定义一个 run 方法,以此执行您在该线程中要运行的代码。

    4. run 方法中的self.getName() 是一个用于确定该线程名称的方法。

    5. 最后三行代码实际地调用该类,并启动线程。如果注意的话,那么会发现实际启动线程的是 t.start()

 

 

二、使用线程队列

如前所述,当多个线程需要共享数据或者资源的时候,可能会使得线程的使用变得复杂。线程模块提供了许多同步原语,包括信号量、条件变量、事件和锁。当这些 选项存在时,最佳实践是转而关注于使用队列。相比较而言,队列更容易处理,并且可以使得线程编程更加安全,因为它们能够有效地传送单个线程对资源的所有访 问,并支持更加清晰的、可读性更强的设计模式。

在下一个示例中,我们的目的是:获取网站的 URL,并显示页面的前 300 个字节。

先看看串行方式或者依次执行实现的代码:

from urllib import request
import time

hosts = ["http://yahoo.com", "http://amazon.com", "http://ibm.com", "http://apple.com"]

start = time.time()
#grabs urls of hosts and prints first 1024 bytes of page
for host in hosts:
    url = request.urlopen(host)
    print(url.read(200))

print("Elapsed Time: %s" % (time.time() - start))

    代码解读

    1. urllib 模块减少了获取 Web 页面的复杂程度。两次 time.time() 用于计算程序运行时间。

    2. 这个程序的执行速度是 13.7 秒,这个结果并不算太好,也不算太糟。

    3. 但如果需要检索数百个 Web 页面,那么按照这个平均值,总时间需要花费大约 1000 秒的时间。如果需要检索更多页面呢?

 

 

 下面给出线程化版本:

import queue
import threading
from urllib import request
import time

hosts = ["http://yahoo.com", "http://amazon.com", "http://ibm.com", "http://apple.com"]

in_queue = queue.Queue()

class ThreadUrl(threading.Thread):
    """Threaded Url Grab"""
    def __init__(self, in_queue):
        threading.Thread.__init__(self)
        self.in_queue = in_queue

    def run(self):
        while True:
            #grabs host from queue
            host = self.in_queue.get()
          
            #grabs urls of hosts and prints first 1024 bytes of page
            url = request.urlopen(host)
            print(url.read(200))
          
            #signals to queue job is done
            self.in_queue.task_done()

start = time.time()

def main():

    #spawn a pool of threads, and pass them queue instance 
    for i in range(4):
        t = ThreadUrl(in_queue)
        t.setDaemon(True)
        t.start()
    
    #populate queue with data   
        for host in hosts:
            in_queue.put(host)
     
    #wait on the queue until everything has been processed     
    in_queue.join()

main()
print("Elapsed Time: %s" % (time.time() - start))

    代码解读

    1. 与第一个线程示例相比,它并没有复杂多少,因为使用了队列模块。

    2. 创建一个 queue.Queue() 的实例,然后使用数据对它进行填充。

    3. 将经过填充数据的实例传递给线程类,后者是通过继承 threading.Thread 的方式创建的。

    4. 生成守护线程池。

    5. 每次从队列中取出一个项目,并使用该线程中的数据和 run 方法以执行相应的工作。

    6. 在完成这项工作之后,使用 queue.task_done() 函数向任务已经完成的队列发送一个信号。

    7. 对队列执行 join 操作,实际上意味着等到队列为空,再退出主程序。

在使用这个模式时需要注意一点:通过将守护线程设置为 true,将允许主线程或者程序仅在守护线程处于活动状态时才能够退出。这种方式创建了一种简单的方式以控制程序流程,因为在退出之前,您可以对队列执行 join 操作、或者等到队列为空。

join()保持阻塞状态,直到处理了队列中的所有项目为止。在将一个项目添加到该队列时,未完成的任务的总数就会增加。当使用者线程调用 task_done() 以表示检索了该项目、并完成了所有的工作时,那么未完成的任务的总数就会减少。当未完成的任务的总数减少到零时,join() 就会结束阻塞状态。

 

 

 

三、使用多个队列

 下一个示例有两个队列。其中一个队列的各线程获取的完整 Web 页面,然后将结果放置到第二个队列中。然后,对加入到第二个队列中的另一个线程池进行设置,然后对 Web 页面执行相应的处理。

提取所访问的每个页面的 title 标记,并将其打印输出。

import queue
import threading
from urllib import request
import time
from bs4 import BeautifulSoup

hosts = ["http://yahoo.com", "http://amazon.com", "http://ibm.com", "http://apple.com"]

in_queue = queue.Queue()
out_queue = queue.Queue()

class ThreadUrl(threading.Thread):
    """Threaded Url Grab"""
    def __init__(self, in_queue, out_queue):
        threading.Thread.__init__(self)
        self.in_queue = in_queue
        self.out_queue = out_queue

    def run(self):
        while True:
            #grabs host from queue
            host = self.in_queue.get()

            #grabs urls of hosts and then grabs chunk of webpage
            url = request.urlopen(host)
            chunk = url.read()

            #place chunk into out queue
            self.out_queue.put(chunk)

            #signals to queue job is done
            self.in_queue.task_done()

class DatamineThread(threading.Thread):
    """Threaded Url Grab"""
    def __init__(self, out_queue):
        threading.Thread.__init__(self)
        self.out_queue = out_queue

    def run(self):
        while True:
            #grabs host from queue
            chunk = self.out_queue.get()

            #parse the chunk
            soup = BeautifulSoup(chunk)
            print(soup.findAll(['title']))

            #signals to queue job is done
            self.out_queue.task_done()

start = time.time()

def main():

    #spawn a pool of threads, and pass them queue instance
    for i in range(4):
        t = ThreadUrl(in_queue, out_queue)
        t.setDaemon(True)
        t.start()

    #populate queue with data
    for host in hosts:
        in_queue.put(host)

    for i in range(4):
        dt = DatamineThread(out_queue)
        dt.setDaemon(True)
        dt.start()


    #wait on the queue until everything has been processed
    in_queue.join()
    out_queue.join()

main()
print("Elapsed Time: %s" % (time.time() - start))

    代码解读

    1. 我们添加了另一个队列实例,然后将该队列传递给第一个线程池类 ThreadURL

    2. 对于另一个线程池类 DatamineThread, 几乎复制了完全相同的结构。

    3. 在这个类的 run 方法中,从队列中的各个线程获取 Web 页面、文本块,然后使用 Beautiful Soup 处理这个文本块。

    4. 使用 Beautiful Soup 提取每个页面的 title 标记、并将其打印输出。

    5. 可以很容易地将这个示例推广到一些更有价值的应用场景,因为您掌握了基本搜索引擎或者数据挖掘工具的核心内容。

    6. 一种思想是使用 Beautiful Soup 从每个页面中提取链接,然后按照它们进行导航。

 

目录
相关文章
|
2月前
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
187 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
1月前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
62 20
|
5月前
|
安全 数据处理 开发者
Python中的多线程编程:从入门到精通
本文将深入探讨Python中的多线程编程,包括其基本原理、应用场景、实现方法以及常见问题和解决方案。通过本文的学习,读者将对Python多线程编程有一个全面的认识,能够在实际项目中灵活运用。
|
20天前
|
数据采集 Java 数据处理
Python实用技巧:轻松驾驭多线程与多进程,加速任务执行
在Python编程中,多线程和多进程是提升程序效率的关键工具。多线程适用于I/O密集型任务,如文件读写、网络请求;多进程则适合CPU密集型任务,如科学计算、图像处理。本文详细介绍这两种并发编程方式的基本用法及应用场景,并通过实例代码展示如何使用threading、multiprocessing模块及线程池、进程池来优化程序性能。结合实际案例,帮助读者掌握并发编程技巧,提高程序执行速度和资源利用率。
23 0
|
4月前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
4月前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
4月前
|
Java Unix 调度
python多线程!
本文介绍了线程的基本概念、多线程技术、线程的创建与管理、线程间的通信与同步机制,以及线程池和队列模块的使用。文章详细讲解了如何使用 `_thread` 和 `threading` 模块创建和管理线程,介绍了线程锁 `Lock` 的作用和使用方法,解决了多线程环境下的数据共享问题。此外,还介绍了 `Timer` 定时器和 `ThreadPoolExecutor` 线程池的使用,最后通过一个具体的案例展示了如何使用多线程爬取电影票房数据。文章还对比了进程和线程的优缺点,并讨论了计算密集型和IO密集型任务的适用场景。
168 4
|
5月前
|
Python
Python中的多线程与多进程
本文将探讨Python中多线程和多进程的基本概念、使用场景以及实现方式。通过对比分析,我们将了解何时使用多线程或多进程更为合适,并提供一些实用的代码示例来帮助读者更好地理解这两种并发编程技术。
|
4月前
|
监控 JavaScript 前端开发
python中的线程和进程(一文带你了解)
欢迎来到瑞雨溪的博客,这里是一位热爱JavaScript和Vue的大一学生分享技术心得的地方。如果你从我的文章中有所收获,欢迎关注我,我将持续更新更多优质内容,你的支持是我前进的动力!🎉🎉🎉
57 0
|
4月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
137 0

热门文章

最新文章