python中的并发编程-进程、线程2

发布于:2024-04-24 ⋅ 阅读:(15) ⋅ 点赞:(0)

继续上一遍文章的探讨:https://blog.csdn.net/weixin_39743356/article/details/137563046

进程&线程的基本方法

线程方法

start()

此方法用于启动线程。一个线程必须被启动后才能执行。在调用start()方法后,线程会开始执行它的target函数。

  • 当调用start()方法时,线程会被创建并启动,开始执行线程的任务。
  • start()方法会在后台调用线程的run()方法,而不会直接执行run()方法。
  • 调用start()方法后,线程会在后台执行,主线程可以继续执行其他任务。
import threading
from threading import Thread
import time


def my_target_func():
    print(f"开始子线程【{threading.currentThread().getName()}】,并等待3秒")
    time.sleep(3)
    print(f"等待3秒后结束子线程【{threading.currentThread().getName()}】")


thread = Thread(target=my_target_func)

print(f"主线程【{threading.currentThread().getName()}】开始执行")

# 启动线程,会创建新的线程并执行自定义的target方法
thread.start()

# 直接在本线程执行定义的target方法,不会创建新的线程
# thread.run()

print(f"主线程【{threading.currentThread().getName()}】结束")

输出:

主线程【MainThread】开始执行
开始子线程【Thread-1】,并等待3秒
主线程【MainThread】结束
等待3秒后结束子线程【Thread-1】

run()

  • run()方法定义了线程要执行的任务,是线程的入口点。
  • 当直接调用run()方法时,该方法会在当前线程中执行,并不会启动一个新的线程。
  • 通常情况下,我们不直接调用run()方法,而是通过调用start()方法来启动线程并执行run()方法。
# 代码同上,注掉start,打开run即可

输出:

主线程【MainThread】开始执行
开始子线程【MainThread】,并等待3秒
等待3秒后结束子线程【MainThread】
主线程【MainThread】结束

join()

join()方法用于等待线程完成执行。调用此方法的线程会被阻塞,直到调用join()的线程完成执行。这通常用于确保主线程等待所有子线程完成。

...以上重复代码

# 启动线程,会创建新的线程并执行自定义的target方法
thread.start()

# 调用后,当前main线程会等待thread执行完成,否则直接向下执行
thread.join()
# thread.join(1)  # 可以传入timeout参数,代表等待多少秒超时,一旦超时,主线程将不再阻塞,继续往下执行

print(f"主线程【{threading.currentThread().getName()}】结束")

不调用join的输出:

主线程【MainThread】开始执行
开始子线程【Thread-1】,并等待3秒
等待3秒后结束子线程【Thread-1】
主线程【MainThread】结束

调用join的输出:

主线程【MainThread】开始执行
开始子线程【Thread-1】,并等待3秒
等待3秒后结束子线程【Thread-1】
主线程【MainThread】结束

is_alive()

此方法用于检查线程是否仍在执行。如果线程已经完成或尚未启动,它会返回False;如果线程正在运行,它会返回True

...以上重复代码

# 启动线程,会创建新的线程并执行自定义的target方法
thread.start()

print(thread.is_alive())

# 调用后,当前main线程会等待thread执行完成,否则直接向下执行
thread.join()

print(thread.is_alive())

print(f"主线程【{threading.currentThread().getName()}】结束")

输出:

主线程【MainThread】开始执行
开始子线程【Thread-1】,并等待3秒
True
等待3秒后结束子线程【Thread-1】
False
主线程【MainThread】结束

进程方法

start、run、join、is_alive与线程的差不多

terminate()

terminate()方法用于立即停止进程。

当调用这个方法时,Python会向该进程发送一个信号(异步非阻塞),导致进程无条件地立即停止执行。

  1. 不安全性:使用 terminate() 可能非常危险,因为它会导致被终止的进程立即停止执行。如果该过程正在执行关键代码或者进行资源清理、文件写入等操作时被终结,则可能导致数据损坏或者资源泄露。
  2. 资源清理:由于不会正常退出运行循环或完成当前工作项,所以不会触发任何try/finally块内部的清理代码。如果你有需要释放或关闭资源(如文件句柄、网络连接、数据库连接等),则应考虑使用其他方式优雅地关闭线程。
import multiprocessing
import os
import time


def worker(num):
    """线程工作函数"""
    pid = os.getpid()
    ppid = os.getppid()
    print(f'Worker: {num} 启动,即将阻塞3s,当前pid: {pid} , 父pid: {ppid} ')
    time.sleep(3)


if __name__ == '__main__':
    pid = os.getpid()
    print(f" 主进程 ,pid:{pid}")

    # 创建进程
    p = multiprocessing.Process(target=worker, args=(1,))

    print("p.name:" + p.name)

    print(p.is_alive())  # False
    p.start()  # 异步非阻塞
    print(p.is_alive())  # True
    p.terminate()  # 异步非阻塞
    print(p.is_alive())  # True
    time.sleep(1)
    print(p.is_alive())  # False

    print("主进程结束")

  1. 打印进程的初始状态:

        print(p.is_alive())  # False
    

    使用is_alive()方法检查进程p是否正在运行。在启动之前,它会返回False

  2. 启动进程:

        p.start()  # 异步非阻塞
    

    调用start()方法启动进程。这是一个异步操作,不会等待进程结束。

  3. 再次检查进程状态:

        print(p.is_alive())  # True
    

    进程启动后,is_alive()方法将返回True,表示进程正在运行。

  4. 调用terminate()方法:

        p.terminate()  # 异步非阻塞
    

    terminate()方法用于请求终止进程。这是一个异步操作,它会向进程发送一个终止信号,但不会立即停止进程。进程可能会在完成当前正在执行的操作后正常退出,或者可能需要一些时间来响应终止请求。

  5. 再次检查进程状态:

        print(p.is_alive())  # True
    

    由于terminate()是异步的,进程可能还没有立即停止,所以is_alive()可能仍然返回True

  6. 等待一秒:

       time.sleep(1)
    

    等待一秒,以便给进程足够的时间来响应终止请求。

  7. 最后检查进程状态:

       print(p.is_alive())  # False
    

    经过等待后,is_alive()方法应该返回False,表示进程已经停止。

进程的结束

常见的导致进程结束的情况:

  1. 正常退出
    当程序执行到最后,或者调用了sys.exit()函数时,进程会正常退出。sys.exit()函数允许你指定一个退出码(默认为0),这个退出码可以传递给操作系统,表示程序的退出状态。例如:

    import sys
    sys.exit(0)  # 正常退出,返回码0
    
  2. 异常退出
    如果在程序执行过程中发生了未捕获的异常,Python进程将会异常退出。这通常意味着程序遇到了无法恢复的错误。例如,试图除以零将引发ZeroDivisionError异常,导致进程退出:

    try:
        1 / 0  # 这会引发ZeroDivisionError异常
    except ZeroDivisionError:
        print("Caught an exception, exiting.")
        sys.exit(1)  # 异常退出,返回码1
    
  3. 被操作系统杀死
    操作系统可能会因为多种原因(如资源限制、用户请求、系统维护等)强制结束进程。例如,用户可能通过任务管理器或命令行工具(如kill命令在UNIX系统或taskkill命令在Windows系统)来结束进程。

  4. 父进程结束
    在多进程程序中,如果父进程结束,子进程可能会成为孤儿进程,并被操作系统接管。在UNIX系统中,孤儿进程会被init进程接管,而在Windows系统中,孤儿进程会被NT AUTHORITY\SYSTEM接管。这些进程会尝试回收子进程的资源,但可能会导致子进程的异常退出。

  5. 信号处理
    Python进程可以通过信号处理来响应来自操作系统或用户的信号。例如,SIGINT信号(通常是通过按下Ctrl+C触发)可以用来请求程序终止。默认情况下,Python会将SIGINT信号映射到KeyboardInterrupt异常,可以通过捕获这个异常来优雅地退出程序:

    import signal
    try:
        # 程序主逻辑
    except KeyboardInterrupt:
        print("Exiting due to KeyboardInterrupt.")
        sys.exit(0)
    
  6. 资源耗尽
    如果进程耗尽了系统资源(如内存、CPU时间等),它可能会被操作系统强制结束。例如,操作系统可能会实施内存限制,当进程尝试分配超过限制的内存时,操作系统会终止该进程。

  7. 程序逻辑决定退出
    程序可能会根据某些条件或用户输入来决定退出。例如,程序可能会检查一个配置文件或命令行参数,如果满足某些条件,则调用sys.exit()来退出。

  8. 子进程结束
    在多进程程序中,如果所有子进程都结束了,父进程可能会随之结束。可以通过调用multiprocessing.active_children()来检查是否有活跃的子进程。

了解这些进程结束的情况对于编写健壮的、能够优雅处理退出的Python程序非常重要。在实际编程中,应该尽量确保程序能够处理所有可能导致退出的情况,并在退出前进行必要的清理工作,如保存状态、释放资源等。

多进程的使用过程中, 如果没有正常的结束进程,则可能会产生僵尸进程或孤儿进程的, 我们下一篇再详细探讨。