篇主在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 timedef 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多进程 多线程 协程
-
多线程:Python 标准库中的
threading
模块提供了多线程支持(上面的实验就是使用的它)。由于全局解释器锁(GIL)的存在,CPython 中的多线程无法充分利用多核 CPU,因此在 CPU 密集型任务中表现不佳。但在 I/O 密集型任务中,多线程可以提高程序的执行效率。 -
多进程:Python 的
multiprocessing
模块提供了多进程支持。多进程可以充分利用多核 CPU,适用于 CPU 密集型任务。然而,进程间通信和资源共享相对复杂,开销较大。 -
协程:协程是一种轻量级的并发策略,它允许在一个线程内部实现多个任务的并发执行。协程通过异步 I/O 和
async/await
语法实现。Python 的asyncio
模块提供了协程支持。协程适用于 I/O 密集型任务,如网络请求、文件读写等。
多进程实验:
import multiprocessing
import timedef count(n):while n > 0:n -= 1def 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.2506167888641357if __name__ == '__main__':main1()
协程实验(该实验中,多协程并不占优,这是因为协程的主要优势在于能够高效地处理I/O密集型任务,如网络请求、文件读写等。):
import asyncio
import timeasync def count(n):while n > 0:n -= 1async 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守护进程 守护线程
-
守护进程:守护进程是一种在后台运行的进程,不受用户交互的影响。守护进程通常用于执行后台任务,如日志记录、监控等。在 Python 中,可以使用
multiprocessing.Process
类的daemon
属性来创建守护进程。 -
守护线程:守护线程是一种在后台运行的线程,当主线程退出时,守护线程会自动退出。守护线程通常用于执行后台任务,如日志记录、监控等。在 Python 中,可以使用
threading.Thread
类的daemon
属性来创建守护线程。
守护进程实验:
"""
守护进程
但这段代码不适合在windows OS上运行,适合linux
"""
import os
import sys
import timedef 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 timedef run():while True:print("Daemon thread is running...")time.sleep(5)if __name__ == "__main__":daemon_thread = threading.Thread(target=run) # 线程对象daemon_thread.daemon = Truedaemon_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命令下运行时,通常会启动两个线程。这两个线程的主要目的是:
-
主线程:这个线程负责处理HTTP请求,接收客户端发来的请求,然后调用相应的视图函数处理请求,最后返回响应给客户端。在这个过程中,主线程会处理URL路由、模板渲染、数据库操作等任务。
-
自动重新加载线程(Auto-reloader thread):这个线程主要负责监视项目中的源代码文件。当检测到文件发生更改时,它会自动重新加载项目,使得更改立即生效,无需手动重启服务器。这对于开发过程中的调试和快速迭代非常有帮助。
这样的设计可以使得开发者在开发过程中更加高效,因为当代码发生变化时,服务器会自动重新加载,而无需手动重启。同时,通过将自动重新加载功能放在一个单独的线程中,可以确保主线程始终专注于处理HTTP请求,提高服务器的响应速度。