守护?全局解释器锁?Python那些若隐若现的进程线程知识-开发技巧IX

        篇主在web开发中其实用得不多。在当下云平台盛行,依靠平台能力,CI/CD式做法开多个worker工作进程完事,除一些监控日志插件会用多线程多进程,web开发是比较少用的。

        先讲一个python开发都听过的:GIL,即全局解释器锁。

目录

GIL全局解释器锁

Python多进程 多线程 协程

Python守护进程 守护线程

鸭子模型

runserver运行时启动的两个线程是为什么


GIL全局解释器锁

        GIL(全局解释器锁,Global Interpreter Lock)是 CPython 解释器中的一种同步机制,用于限制多线程在同一时刻只能有一个线程执行 Python 字节码。GIL 的存在是为了简化内存管理和解决多线程中的数据竞争问题。

        然而,GIL 也导致了 CPython 中的多线程无法充分利用多核 CPU,因此在 CPU 密集型任务中表现不佳。对于 I/O 密集型任务,可以使用多线程、协程或异步 I/O 来实现并发。对于 CPU 密集型任务,可以考虑使用多进程来充分利用多核 CPU。

        实验:以下是拿python3.6做的使用,单线程和多线程情况下,执行时间十分之相近。(查网上有不少执行结果是还不如单线程的执行时间)
        注:GIL仅适用于CPython解释器。如用Jython和IronPython,并未实现GIL,因此在这些实现中,多线程性能可能会更好。

import threading
import time

def count(n):
    while n > 0:
        n -= 1

# 单线程执行
start_time = time.time()
count(100000000)
end_time = time.time()
print("单线程执行时间:", end_time - start_time)  # 单线程执行时间: 4.452801704406738

# 多线程执行
start_time = time.time()
t1 = threading.Thread(target=count, args=(50000000,))
t2 = threading.Thread(target=count, args=(50000000,))
t1.start()
t2.start()
t1.join()
t2.join()
end_time = time.time()
print("多线程执行时间:", end_time - start_time)  # 多线程执行时间: 4.272630214691162

Python多进程 多线程 协程

  1. 多线程:Python 标准库中的 threading 模块提供了多线程支持(上面的实验就是使用的它)。由于全局解释器锁(GIL)的存在,CPython 中的多线程无法充分利用多核 CPU,因此在 CPU 密集型任务中表现不佳。但在 I/O 密集型任务中,多线程可以提高程序的执行效率。

  2. 多进程:Python 的 multiprocessing 模块提供了多进程支持。多进程可以充分利用多核 CPU,适用于 CPU 密集型任务。然而,进程间通信和资源共享相对复杂,开销较大。

  3. 协程:协程是一种轻量级的并发策略,它允许在一个线程内部实现多个任务的并发执行。协程通过异步 I/O 和 async/await 语法实现。Python 的 asyncio 模块提供了协程支持。协程适用于 I/O 密集型任务,如网络请求、文件读写等。

多进程实验:

import multiprocessing
import time

def count(n):
    while n > 0:
        n -= 1

def main1():
    # 单进程执行
    start_time = time.time()
    count(100000000)
    end_time = time.time()
    print("单进程执行时间:", end_time - start_time)  # 单进程执行时间: 4.279423713684082

    # 多进程执行
    start_time = time.time()
    process1 = multiprocessing.Process(target=count, args=(50000000,))
    process2 = multiprocessing.Process(target=count, args=(50000000,))
    process1.start()
    process2.start()
    process1.join()
    process2.join()
    end_time = time.time()
    print("多进程执行时间:", end_time - start_time)  # 多进程执行时间: 2.2506167888641357

if __name__ == '__main__':
    main1()

协程实验(该实验中,多协程并不占优,这是因为协程的主要优势在于能够高效地处理I/O密集型任务,如网络请求、文件读写。):

import asyncio
import time

async def count(n):
    while n > 0:
        n -= 1

async def main():
    task1 = asyncio.create_task(count(50000000))
    task2 = asyncio.create_task(count(50000000))
    await asyncio.gather(task1, task2)

# 单协程执行
start_time = time.time()
asyncio.run(count(100000000))
end_time = time.time()
print("单协程执行时间:", end_time - start_time)

# 多协程执行
start_time = time.time()
asyncio.run(main())
end_time = time.time()
print("多协程执行时间:", end_time - start_time)

"""
单协程执行时间: 4.209939241409302
多协程执行时间: 4.172176122665405
"""

Python守护进程 守护线程

  1. 守护进程:守护进程是一种在后台运行的进程,不受用户交互的影响。守护进程通常用于执行后台任务,如日志记录、监控等。在 Python 中,可以使用 multiprocessing.Process 类的 daemon 属性来创建守护进程。

  2. 守护线程:守护线程是一种在后台运行的线程,当主线程退出时,守护线程会自动退出。守护线程通常用于执行后台任务,如日志记录、监控等。在 Python 中,可以使用 threading.Thread 类的 daemon 属性来创建守护线程。

守护进程实验:

"""
守护进程
但这段代码不适合在windows OS上运行,适合linux
"""
import os
import sys
import time


def daemonize():
    pid = os.fork()  # 创建一个新的子进程

    if pid > 0:
        sys.exit()

    os.setsid()  # 创建一个新的会话,并将子进程设置为该会话的会话领导者。这将使子进程脱离控制终端,从而实现守护进程的特性之一。
    os.umask(0)  # 设置子进程的文件创建模式

    pid = os.fork()

    if pid > 0:
        sys.exit()

    sys.stdout.flush()
    sys.stderr.flush()

    with open("/dev/null", "r") as stdin:
        os.dup2(stdin.fileno(), sys.stdin.fileno())

    with open("/dev/null", "a") as stdout:
        os.dup2(stdout.fileno(), sys.stdout.fileno())

    with open("/dev/null", "a") as stderr:
        os.dup2(stderr.fileno(), sys.stderr.fileno())


def run():
    while True:
        print("Daemon is running...")
        time.sleep(5)


if __name__ == "__main__":
    daemonize()
    run()

守护线程实验:

import threading
import time


def run():
    while True:
        print("Daemon thread is running...")
        time.sleep(5)


if __name__ == "__main__":
    daemon_thread = threading.Thread(target=run)  # 线程对象
    daemon_thread.daemon = True
    daemon_thread.start()  # 启动守护线程

    # 主线程将等待10秒后结束
    time.sleep(10)
    print("Main thread is terminating")

"""
Daemon thread is running...
Daemon thread is running...
Main thread is terminating
Daemon thread is running...
"""

鸭子模型

        鸭子模型(Duck Typing)是一种编程概念,主要用于动态类型语言(如 Python)。鸭子模型的核心思想是关注对象的行为,而不是关注对象的类型。换句话说,如果一个对象像鸭子一样走路、叫声,那么我们就认为它是鸭子,而不关心它的实际类型。

class Duck:
    def quack(self):
        return "Quack!"

class Dog:
    def quack(self):
        return "Woof!"

def make_sound(animal):
    print(animal.quack())

duck = Duck()
dog = Dog()

make_sound(duck)  # 输出 "Quack!"
make_sound(dog)   # 输出 "Woof!"

runserver运行时启动的两个线程是为什么

在Django的runserver命令下运行时,通常会启动两个线程。这两个线程的主要目的是:

  1. 主线程:这个线程负责处理HTTP请求,接收客户端发来的请求,然后调用相应的视图函数处理请求,最后返回响应给客户端。在这个过程中,主线程会处理URL路由、模板渲染、数据库操作等任务。

  2. 自动重新加载线程(Auto-reloader thread):这个线程主要负责监视项目中的源代码文件。当检测到文件发生更改时,它会自动重新加载项目,使得更改立即生效,无需手动重启服务器。这对于开发过程中的调试和快速迭代非常有帮助

        这样的设计可以使得开发者在开发过程中更加高效,因为当代码发生变化时,服务器会自动重新加载,而无需手动重启。同时,通过将自动重新加载功能放在一个单独的线程中,可以确保主线程始终专注于处理HTTP请求,提高服务器的响应速度。

猜你喜欢

转载自blog.csdn.net/lxd_max/article/details/132092041