广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Python中怎样实现多线程
  • 900
分享到

Python中怎样实现多线程

2023-06-16 19:06:15 900人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

今天就跟大家聊聊有关python中怎样实现多线程,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。线程简介多线程能让你像运行一个独立的程序一样运行一段长代码。这有点像调用子进程(subp

今天就跟大家聊聊有关python中怎样实现多线程,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

线程简介

线程能让你像运行一个独立的程序一样运行一段长代码。这有点像调用子进程(subprocess),不过区别是你调用的是一个函数或者一个类,而不是独立的程序。在我看来,举例说明更有助于解释。下面来看一个简单的例子:

import threading  def doubler(number): """ 可以被线程使用的一个函数 """ print(threading.currentThread.getName + '\n') print(number * 2) print  if __name__ == '__main__': for i in range(5): my_thread = threading.Thread(target=doubler, args=(i,)) my_thread.start

这里,我们导入 threading 模块并且创建一个叫  doubler的常规函数。这个函数接受一个值,然后把这个值翻一番。它还会打印出调用这个函数的线程的名称,并在最后打印一行空行。然后在代码的最后一块,我们创建五个线程并且依次启动它们。在我们实例化一个线程时,你会注意到,我们把  doubler 函数传给target参数,同时也给 doubler 函数传递了参数。Args参数看起来有些奇怪,那是因为我们需要传递一个序列给 doubler  函数,但它只接受一个变量,所以我们把逗号放在尾部来创建只有一个参数的序列。

需要注意的是,如果你想等待一个线程结束,那么需要调用 join方法。

当你运行以上这段代码,会得到以下输出内容:

Thread-1  0  Thread-2  2  Thread-3  4  Thread-4  6  Thread-5  8

当然,通常情况下你不会希望输出打印到标准输出。如果不幸真的这么做了,那么最终的显示效果将会非常混乱。你应该使用 Python 的 logging  模块。它是线程安全的,并且表现出色。让我们用 logging模块修改上面的例子并且给我们的线程命名。代码如下:

import logging import threading  def get_logger: logger = logging.getLogger("threading_example") logger.setLevel(logging.DEBUG)  fh = logging.FileHandler("threading.log") fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s' fORMatter = logging.Formatter(fmt) fh.setFormatter(formatter)  logger.addHandler(fh) return logger  def doubler(number, logger): """ 可以被线程使用的一个函数 """ logger.debug('doubler function executing') result = number * 2 logger.debug('doubler function ended with: {}'.format( result))  if __name__ == '__main__': logger = get_logger thread_names = ['Mike', 'George', 'Wanda', 'Dingbat', 'Nina'] for i in range(5): my_thread = threading.Thread( target=doubler, name=thread_names[i], args=(i,logger)) my_thread.start

代码中最大的改变就是加入了  get_logger函数。这段代码将创建一个被设置为调试级别的日志记录器。它将日志保存在当前目录(即脚本运行所在的目录)下,然后设置每行日志的格式。格式包括时间戳、线程名、日志记录级别以及日志信息。

在 doubler 函数中,我们把 print语句换成 logging 语句。你会注发现,在创建线程时,我们给 doubler 函数传入了 logger  对象。这样做的原因是,如果在每个线程中实例化 logging 对象,那么将会产生多个 logging  单例(singleton),并且日志中将会有很多重复的内容。

最后,创建一个名称列表,然后使用 name关键字参数为每一个线程设置具体名称,这样就可以为线程命名。运行以上代码,将会得到包含以下内容的日志文件:

2016-07-24 20:39:50,055 - Mike - DEBUG - doubler function executing 2016-07-24 20:39:50,055 - Mike - DEBUG - doubler function ended with: 0 2016-07-24 20:39:50,055 - George - DEBUG - doubler function executing 2016-07-24 20:39:50,056 - George - DEBUG - doubler function ended with: 2 2016-07-24 20:39:50,056 - Wanda - DEBUG - doubler function executing 2016-07-24 20:39:50,056 - Wanda - DEBUG - doubler function ended with: 4 2016-07-24 20:39:50,056 - Dingbat - DEBUG - doubler function executing 2016-07-24 20:39:50,057 - Dingbat - DEBUG - doubler function ended with: 6 2016-07-24 20:39:50,057 - Nina - DEBUG - doubler function executing 2016-07-24 20:39:50,057 - Nina - DEBUG - doubler function ended with: 8

输出结果不言自明,所以继续介绍其他内容。在本节中再多说一点,即通过继承 threading.Thread实现多线程。举最后一个例子,通过继承  threading.Thread 创建子类,而不是直接调用 Thread 函数。

更新后的代码如下:

import logging import threading  class MyThread(threading.Thread): def __init__(self, number, logger): threading.Thread.__init__(self) self.number = number self.logger = logger  def run(self): """ 运行线程 """ logger.debug('Calling doubler') doubler(self.number, self.logger)  def get_logger: logger = logging.getLogger("threading_example") logger.setLevel(logging.DEBUG)  fh = logging.FileHandler("threading_class.log") fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s' formatter = logging.Formatter(fmt) fh.setFormatter(formatter)  logger.addHandler(fh) return logger  def doubler(number, logger): """ 可以被线程使用的一个函数 """ logger.debug('doubler function executing') result = number * 2 logger.debug('doubler function ended with: {}'.format( result))  if __name__ == '__main__': logger = get_logger thread_names = ['Mike', 'George', 'Wanda', 'Dingbat', 'Nina'] for i in range(5): thread = MyThread(i, logger) thread.setName(thread_names[i]) thread.start

这个例子中,我们只是创建一个继承于 threading.Thread的子类。像之前一样,传入一个需要翻一番的数字,以及 logging  对象。但是这次,设置线程名称的方式有点不太一样,变成了通过调用 thread  对象的setName方法来设置。不过仍然需要调用start来启动线程,不过你可能注意到我们并不需要在子类中定义该方法。当调用start时,它会通过调用run方法来启动线程。在我们的类中,我们调用  doubler 函数来做处理。输出结果中除了一些添加的额外信息内容几乎差不多。运行下这个脚本,看看你会得到什么。

线程锁与线程同步

当你有多个线程,就需要考虑怎样避免线程冲突。我的意思是说,你可能遇到多个线程同时访问同一资源的情况。如果不考虑这些问题并且制定相应的解决方案,那么在开发产品过程中,你总会在最糟糕的时候遇到这些棘手的问题。

解决办法就是使用线程。锁由 Python 的 threading  模块提供,并且它最多被一个线程所持有。当一个线程试图获取一个已经锁在资源上的锁时,该线程通常会暂停运行,直到这个锁被释放。来让我们看一个非常典型没有却应具备锁功能的例子:

import threading  total = 0  def update_total(amount): """ Updates the total by the given amount """ global total total += amount print (total) if __name__ == '__main__': for i in range(10): my_thread = threading.Thread( target=update_total, args=(5,)) my_thread.start

如果往以上代码添加  time.sleep函数并给出不同长度的时间,可能会让这个例子更有意思。无论如何,这里的问题是,一个线程可能已经调用update_total函数并且还没有更新完成,此时另一个线程也有可能调用它并且尝试更新内容。根据操作执行顺序的不同,该值可能只被增加一次。

让我们给这个函数添加锁。有两种方法可以实现。第一种方式是使用 try/finally,从而确保锁肯定会被释放。下面是示例:

import threading  total = 0  lock = threading.Lock def update_total(amount): """ Updates the total by the given amount """ global total lock.acquire try: total += amount finally: lock.release print (total)  if __name__ == '__main__': for i in range(10): my_thread = threading.Thread( target=update_total, args=(5,)) my_thread.start

如上,在我们做任何处理之前就获取锁。然后尝试更新 total 的值,最后释放锁并打印出 total 的当前值。事实上,我们可以使用 Python 的  with语句避免使用 try/finally 这种较为繁琐的语句:

import threading  total = 0  lock = threading.Lock  def update_total(amount): """ Updates the total by the given amount """ global total with lock: total += amount print (total)  if __name__ == '__main__': for i in range(10): my_thread = threading.Thread( target=update_total, args=(5,)) my_thread.start

正如你看到的那样,我们不再需要 try/finally作为上下文管理器,而是由with语句作为替代。

当然你也会遇到要在代码中通过多个线程访问多个函数的情况。当你第一次编写并发代码时,代码可能是这样的:

import threading  total = 0  lock = threading.Lock def do_something: lock.acquire try: print('Lock acquired in the do_something function') finally: lock.release print('Lock released in the do_something function') return "Done doing something"  def do_something_else: lock.acquire try: print('Lock acquired in the do_something_else function') finally: lock.release print('Lock released in the do_something_else function') return "Finished something else"  if __name__ == '__main__': result_one = do_something result_two = do_something_else

这样的代码在上面的情况下能够正常工作,但假设你有多个线程都调用这两个函数呢。当一个线程正在运行这两个函数,然后另外一个线程也可能会修改这些数据,最后得到的就是不正确的结果。问题是,你甚至可能没有马上意识到结果错了。有什么解决办法呢?让我们试着找出答案。

通常首先想到的就是在调用这两个函数的地方上锁。让我们试着修改上面的例子,修改成如下所示:

import threading  total = 0  lock = threading.RLock def do_something:  with lock: print('Lock acquired in the do_something function') print('Lock released in the do_something function') return "Done doing something"   def do_something_else: with lock: print('Lock acquired in the do_something_else function') print('Lock released in the do_something_else function') return "Finished something else"  def main: with lock: result_one = do_something result_two = do_something_else print (result_one) print (result_two)  if __name__ == '__main__': main

当你真正运行这段代码时,你会发现它只是挂起了。究其原因,是因为我们只告诉 threading  模块获取锁。所以当我们调用第一个函数时,它发现锁已经被获取,随后便把自己挂起了,直到锁被释放,然而这将永远不会发生。

真正的解决办法是使用重入锁(Re-Entrant Lock)。threading 模块提供的解决办法是使用RLock函数。即把lock =  threading.lock替换为lock = threading.RLock,然后重新运行代码,现在代码就可以正常运行了。

如果你想在线程中运行以上代码,那么你可以用以下代码取代直接调用 main函数:

if __name__ == '__main__': for i in range(10): my_thread = threading.Thread( target=main) my_thread.start

每个线程都会运行 main 函数,main 函数则会依次调用另外两个函数。最终也会产生 10 组结果集。

定时器

Threading 模块有一个优雅的  Timer类,你可以用它来实现在指定时间后要发生的动作。它们实际上会启动自己的自定义线程,通过调用常规线程上的start方法即可运行。你也可以调用它的cancel方法停止定时器。值得注意的是,你甚至可以在开始定时器之前取消它。

有一天,我遇到一个特殊的情况:我需要与已经启动的子进程通信,但是我需要它有超时处理。虽然处理这种特殊问题有很多不同的方法,不过我最喜欢的解决方案是使用  threading 模块的 Timer 类。

在下面这个例子中,我们将使用 ping指令作为演示。在 linux 系统中,ping 命令会一直运行下去直到你手动杀死它。所以在 Linux  世界里,Timer 类就显得非常方便。示例如下:

import subprocess from threading import Timer  kill = lambda process: process.kill cmd = ['ping', 'www.Google.com'] ping = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)  my_timer = Timer(5, kill, [ping]) try: my_timer.start stdout, stderr = ping.communicate finally: my_timer.cancel print (str(stdout))

这里我们在 lambda 表达式中调用 kill 杀死进程。接下来启动 ping 命令,然后创建 Timer  对象。你会注意到,第一个参数就是需要等待的秒数,第二个参数是需要调用的函数,紧跟其后的参数是要调用函数的入参。在本例中,我们的函数是一个 lambda  表达式,传入的是一个只有一个元素的列表。如果你运行这段代码,它应该会运行 5 秒钟,然后打印出 ping 的结果。

其他线程组件

Threading  模块包含对其他功能的支持。例如,你可以创建信号量(Semaphore),这是计算机科学中最古老的同步原语之一。基本上,一个信号量管理一个内置的计数器。当你调用acquire时计数器就会递减,相反当你调用release时就会递增。根据其设计,计数器的值无法小于零,所以如果正好在计数器为零时调用  acquire 方法,该方法将阻塞线程。

译者注:通常使用信号量时都会初始化一个大于零的值,如 semaphore = threading.Semaphore(2)

另一个非常有用的同步工具就是事件(Event)。它允许你使用信号(signal)实现线程通信。在下一节中我们将举一个使用事件的实例。

最后,在 Python 3.2 中加入了 Barrier对象。Barrier 是管理线程池中的同步原语,在线程池中多条线程需要相互等待对方。如果要传递  barrier,每一条线程都要调用wait方法,在其他线程调用该方法之前线程将会阻塞。全部调用之后将会同时释放所有线程。

线程通信

某些情况下,你会希望线程之间互相通信。就像先前提到的,你可以通过创建  Event对象达到这个目的。但更常用的方法是使用队列(Queue)。在我们的例子中,这两种方式都会有所涉及。下面让我们看看到底是什么样子的:

import threading from queue import Queue  def creator(data, q): """ 生成用于消费的数据,等待消费者完成处理 """ print('Creating data and putting it on the queue') for item in data: evt = threading.Event q.put((item, evt))  print('Waiting for data to be doubled') evt.wait  def my_consumer(q): """ 消费部分数据,并做处理  这里所做的只是将输入翻一倍  """ while True: data, evt = q.get print('data found to be processed: {}'.format(data)) processed = data * 2 print(processed) evt.set q.task_done  if __name__ == '__main__': q = Queue data = [5, 10, 13, -1] thread_one = threading.Thread(target=creator, args=(data, q)) thread_two = threading.Thread(target=my_consumer, args=(q,)) thread_one.start thread_two.start  q.join

让我们掰开揉碎分析一下。首先,我们有一个创建者(creator)函数(亦称作生产者(producer)),我们用它来创建想要操作(或者消费)的数据。然后用另外一个函数  my_consumer来处理刚才创建出来的数据。Creator 函数使用 Queue  的put方法向队列中插入数据,消费者将会持续不断的检测有没有更多的数据,当发现有数据时就会处理数据。Queue  对象处理所有的获取锁和释放锁的过程,这些不用我们太关心。

在这个例子中,先创建一个列表,然后创建两个线程,一个用作生产者,一个作为消费者。你会发现,我们给两个线程都传递了 Queue  对象,这两个线程隐藏了关于锁处理的细节。队列实现了数据从第一个线程到第二个线程的传递。当第一个线程把数据放入队列时,同时也传递一个 Event  事件,紧接着挂起自己,等待该事件结束。在消费者侧,也就是第二个线程,则做数据处理工作。当完成数据处理后就会调用 Event 事件的  set方法,通知第一个线程已经把数据处理完毕了,可以继续生产了。

看完上述内容,你们对Python中怎样实现多线程有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注编程网Python频道,感谢大家的支持。

--结束END--

本文标题: Python中怎样实现多线程

本文链接: https://www.lsjlt.com/news/284959.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Python中怎样实现多线程
    今天就跟大家聊聊有关Python中怎样实现多线程,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。线程简介多线程能让你像运行一个独立的程序一样运行一段长代码。这有点像调用子进程(subp...
    99+
    2023-06-16
  • Java中怎样实现多线程编程
    今天就跟大家聊聊有关Java中怎样实现多线程编程,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一、理解Java多线程编程Java多线程编程是这样一种机制,它允许在程序中并发执行多个指...
    99+
    2023-06-17
  • php怎样实现多线程
    这篇文章主要为大家展示了“php怎样实现多线程 ”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“php怎样实现多线程 ”这篇文章吧public function ...
    99+
    2022-10-19
  • Java中怎样实现多线程同步
    本篇文章给大家分享的是有关Java中怎样实现多线程同步,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。不同步时的代码Bank.java  package&nb...
    99+
    2023-06-17
  • Python中的多线程怎么实现
    本文小编为大家详细介绍“Python中的多线程怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python中的多线程怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。前言:多线程简单理解就是:一个CP...
    99+
    2023-07-02
  • python怎么实现多线程
    这篇文章将为大家详细讲解有关python怎么实现多线程,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Python的优点有哪些1、简单易用,与C/C++、Java、C# 等传统语言相比,Python对代码格...
    99+
    2023-06-14
  • python多线程及多线程有序性怎么实现
    这篇文章主要介绍了python多线程及多线程有序性怎么实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇python多线程及多线程有序性怎么实现文章都会有所收获,下面我们一起来看看吧。前言多线程一般用于同时调用...
    99+
    2023-07-02
  • 在Java项目中怎么样实现调度多线程
    在Java项目中怎么样实现调度多线程?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。        方法一:设置线程优先级。...
    99+
    2023-05-31
    java 多线程 ava
  • Python中多线程和多处理的指南是怎样的
    Python中多线程和多处理的指南是怎样的,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。使用Python分析数据,如果使用了正确的数据结构和算法,有时可以大量提高程序的速度。实...
    99+
    2023-06-05
  • python中thread模块实现多线程
    这篇文章将为大家详细讲解有关python中thread模块实现多线程,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Python的优点有哪些1、简单易用,与C/C++、Java、C# 等传统语言相比,Pyt...
    99+
    2023-06-14
  • 多线程python的实现及多线程有序性
    目录前言一、多线程运行无序问题二、“join方法”解决多线程运行无序问题三、threading.Thread()的常用参数总结前言 多线程一般用于同时调用多个...
    99+
    2022-11-11
  • Python怎么实现selenium多线程爬虫
    要在Python中实现Selenium多线程爬虫,你可以按照以下步骤进行操作: 导入必要的库: from selenium im...
    99+
    2023-10-24
    Python selenium
  • python thread模块怎么实现多线程
    这篇文章主要介绍“python thread模块怎么实现多线程”,在日常操作中,相信很多人在python thread模块怎么实现多线程问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”python thread...
    99+
    2023-06-30
  • Python进阶之多线程怎么实现
    这篇文章主要介绍“Python进阶之多线程怎么实现”,在日常操作中,相信很多人在Python进阶之多线程怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python进阶之多线程怎么实现”的疑惑有所帮助!...
    99+
    2023-07-06
  • python 多线程简单实现
    1. 线程是什么?线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。2 为什么要用线程?a 单个线程可以在进程中独立运行c 并行操作,适用于C/S架构3 python怎么生成线程(将函数生...
    99+
    2023-01-31
    多线程 简单 python
  • Nodejs中怎么实现多线程
    Nodejs中怎么实现多线程,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1 背景需求中有以下场景1 对称解密、非对称解密2 压缩、解压3...
    99+
    2022-10-19
  • Node.js中怎么实现多线程
    Node.js中怎么实现多线程,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Node.js 是如何工作的Node.js 使用两种线程:ev...
    99+
    2022-10-19
  • VB.NET中怎么实现多线程
    本篇文章为大家展示了VB.NET中怎么实现多线程,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。VB.NET(Visual Basic.NET)是为适应Microsoft .NET框架的需要,对Vis...
    99+
    2023-06-17
  • Java中多线程怎么实现
    这篇文章主要讲解了“Java中多线程怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java中多线程怎么实现”吧!线程是一些可以并行的,独立的执行的代码.之前我编的程序都只能做一件事情...
    99+
    2023-06-03
  • HTML5中怎么实现多线程
    这篇文章将为大家详细讲解有关HTML5中怎么实现多线程,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。一、明确 JavaScript 是单线程JavaScript 语言的一大特点就是单线程,也...
    99+
    2023-06-09
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作