专栏名称: 老齐Py
Data Science
目录
相关文章推荐
沪考一点通  ·  华二6人!2025信奥上海队名单公示! ·  昨天  
姑苏晚报  ·  官方点名通报 ·  昨天  
姑苏晚报  ·  官方点名通报 ·  昨天  
雪漠禅坛  ·  如何看待道德理想主义? ·  2 天前  
雪漠禅坛  ·  如何看待道德理想主义? ·  2 天前  
51好读  ›  专栏  ›  老齐Py

【译】线程:概念和应用(1)

老齐Py  · 掘金  ·  · 2020-02-15 17:33

正文

阅读 22

【译】线程:概念和应用(1)

翻译:老齐

译者注:与本文相关图书推荐:《Python大学实用教程》《跟老齐学Python:轻松入门》

本文将分两部分刊发。

第一部分


Python线程允许程序的不同部分同时运行,并可以简化设计。如果你对Python有一些经验,并且希望使用线程为程序加速,那么本文就是为你准备的!

什么是线程?

线程是一个独立的流,这意味着你的程序可以同时做两件事,但是,对于大多数Python程序,不同的线程实际上并不同时执行,它们只是看起来像是同时执行。

人们很容易认为线程是在程序上运行两个(或更多)不同的处理器,每个处理器同时执行一个独立的任务。这种看法大致正确,线程可能在不同的处理器上运行,但一个处理器一次只能运行一个线程。

要同时运行多个任务,不能用Python的标准方式实现,可以用不同的编程语言,或者多个进程实现,这样做的开发成本就高了。

由于用CPython实现了Python业务,线程可能不会加速所有任务,这是GIL(全称Global Interpreter Lock)的原因,一次只能运行一个Python线程。

如果某项任务需要花费大量时间等待外部事件,那么就可以应用多线程。如果是需要对CPU占用高并且花费很少时间等待外部事件,多线程可能枉费。

对于用Python编写并在标准CPython实现上运行的代码,这是正确的。如果你的线程是用C编写的,那么它们就能够释放GIL、并发运行。如果你在不同的Python实现上运行,也可以查看文档,了解它如何处理线程。

如果你正在运行一个标准的Python程序,只使用Python编写,并且有一个CPU受限的问题,那么你应该用多进程解决此问题。

将程序架构为使用线程也可以提高设计的清晰度。你将在下文中学习的大多数示例不一定会运行得更快,因为它们使用线程。在这些示例中使用线程有助于使设计更清晰、更易于推理。

所以,让我们停止谈论线程并开始使用它!

创建一个线程

现在你已经知道了什么是线程,让我们来学习如何制作线程。Python标准库提供了线程模块 threading ,它包含了你将在本文中看到的大部分内容。在这个模块中, Thread 是对线程的封装,提供了简单的实现接口。

要创建一个线程,需要创建 Thread 的实例,然后调用它的 .start() 方法:

import logging
import threading
import time

def thread_function(name):
    logging.info("Thread %s: starting", name)
    time.sleep(2)
    logging.info("Thread %s: finishing", name)

if __name__ == "__main__":
    format = "%(asctime)s: %(message)s"
    logging.basicConfig(format=format, level=logging.INFO,
                        datefmt="%H:%M:%S")
    logging.info("Main : before creating thread")
    x = threading.Thread(target=thread_function, args=(1,))
    logging.info("Main : before running thread")
    x.start()
    logging.info("Main : wait for the thread to finish")
    # x.join()
    logging.info("Main : all done")
复制代码

如果你查看日志,可以看到在 main 部分正在创建和启动线程:

x = threading.Thread(target=thread_function, args=(1,))
x.start()
复制代码

用函数 thread_function() arg(1,) 创建一个 Thread 实例。在本文中用整数作为线程的名称, threading.get_ident() 可以返回线程的名称,但可读性较差。

thread_function() 函数的作用不大,它只是记录一些日志消息,在这些消息之间加上 time.sleep()

当你执行此程序时,输出将如下所示:

$ ./single_thread.py
Main : before creating thread
Main : before running thread
Thread 1: starting
Main : wait for the thread to finish
Main : all done
Thread 1: finishing
复制代码

你会注意到代码的 main 部分结束之后, Thread 才结束。后面会揭示这么做的原因。

守护线程

在计算机科学中, daemon 是在后台运行的程序。

Python的 threading 模块对 daemon 有更具体的含义。当程序退出时,守护线程会立即关闭。考虑这些定义的一种方法是将 daemon 视为在后台运行的线程,而不必担心关闭它。

如果程序中正在执行的 Threads 不是 daemons ,则程序将在终止之前等待这些线程完成。然而,如果 Threads daemons ,当程序退出时,它们就终止了。

让我们更仔细地看看上面程序的输出,最后两行是有点意思的。当运行这个程序时,在 __main__ 打印完 all done 后以及线程结束之前会暂停大约2秒。

这个暂停是Python等待非后台线程完成。当Python程序结束时,关闭操作是清除线程中的程序。

如果查看 threading 模块的源代码,你将看到 threading._shutdown() 方法,它会遍历所有正在运行的线程,并在每一个没有设置 daemon 标志的线程上调用 .join() 方法。







请到「今天看啥」查看全文