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


    继续上一遍文章的探讨: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()}】结束")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    输出:

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

    run()

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

    输出:

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

    join()

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

    ...以上重复代码
    
    # 启动线程,会创建新的线程并执行自定义的target方法
    thread.start()
    
    # 调用后,当前main线程会等待thread执行完成,否则直接向下执行
    thread.join()
    # thread.join(1)  # 可以传入timeout参数,代表等待多少秒超时,一旦超时,主线程将不再阻塞,继续往下执行
    
    print(f"主线程【{threading.currentThread().getName()}】结束")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    不调用join的输出:

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

    调用join的输出:

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

    is_alive()

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

    ...以上重复代码
    
    # 启动线程,会创建新的线程并执行自定义的target方法
    thread.start()
    
    print(thread.is_alive())
    
    # 调用后,当前main线程会等待thread执行完成,否则直接向下执行
    thread.join()
    
    print(thread.is_alive())
    
    print(f"主线程【{threading.currentThread().getName()}】结束")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输出:

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

    进程方法

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    1. 打印进程的初始状态:

          print(p.is_alive())  # False
      
      • 1

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

    2. 启动进程:

          p.start()  # 异步非阻塞
      
      • 1

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

    3. 再次检查进程状态:

          print(p.is_alive())  # True
      
      • 1

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

    4. 调用terminate()方法:

          p.terminate()  # 异步非阻塞
      
      • 1

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

    5. 再次检查进程状态:

          print(p.is_alive())  # True
      
      • 1

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

    6. 等待一秒:

         time.sleep(1)
      
      • 1

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

    7. 最后检查进程状态:

         print(p.is_alive())  # False
      
      • 1

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

    进程的结束

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

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

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

      try:
          1 / 0  # 这会引发ZeroDivisionError异常
      except ZeroDivisionError:
          print("Caught an exception, exiting.")
          sys.exit(1)  # 异常退出,返回码1
      
      • 1
      • 2
      • 3
      • 4
      • 5
    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)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    6. 资源耗尽
      如果进程耗尽了系统资源(如内存、CPU时间等),它可能会被操作系统强制结束。例如,操作系统可能会实施内存限制,当进程尝试分配超过限制的内存时,操作系统会终止该进程。

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

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

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

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

  • 相关阅读:
    【个人网站搭建】hexo框架Next主题下利用不蒜子统计网站访问次数
    requests 在 Python 3.2 中使用 OAuth 导入失败的问题与解决方案
    laravel高校毕业实习管理系统
    node插件MongoDB(五)—— 库mongoose 的模块化(五)
    CSS Position与Float:探索布局的灵活性
    基于C++实现考试报名系统
    跨平台编译QCA、安装QCA(Windows、Linux、MacOS环境下编译与安装)
    【高等数学】导数与微分
    Python使用.NET开发的类库来提高你的程序执行效率
    R语言dplyr包select函数删除dataframe数据中的以指定字符串开头的数据列(变量)
  • 原文地址:https://blog.csdn.net/weixin_39743356/article/details/137885419