篇主在web开发中其实用得不多。在当下云平台盛行,依靠平台能力,CI/CD式做法开多个worker工作进程完事,除一些监控日志插件会用多线程多进程,web开发是比较少用的。
先讲一个python开发都听过的:GIL,即全局解释器锁。
目录
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多进程 多线程 协程
-
多线程:Python 标准库中的
threading
模块提供了多线程支持(上面的实验就是使用的它)。由于全局解释器锁(GIL)的存在,CPython 中的多线程无法充分利用多核 CPU,因此在 CPU 密集型任务中表现不佳。但在 I/O 密集型任务中,多线程可以提高程序的执行效率。 -
多进程:Python 的
multiprocessing
模块提供了多进程支持。多进程可以充分利用多核 CPU,适用于 CPU 密集型任务。然而,进程间通信和资源共享相对复杂,开销较大。 -
协程:协程是一种轻量级的并发策略,它允许在一个线程内部实现多个任务的并发执行。协程通过异步 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守护进程 守护线程
-
守护进程:守护进程是一种在后台运行的进程,不受用户交互的影响。守护进程通常用于执行后台任务,如日志记录、监控等。在 Python 中,可以使用
multiprocessing.Process
类的daemon
属性来创建守护进程。 -
守护线程:守护线程是一种在后台运行的线程,当主线程退出时,守护线程会自动退出。守护线程通常用于执行后台任务,如日志记录、监控等。在 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命令下运行时,通常会启动两个线程。这两个线程的主要目的是:
-
主线程:这个线程负责处理HTTP请求,接收客户端发来的请求,然后调用相应的视图函数处理请求,最后返回响应给客户端。在这个过程中,主线程会处理URL路由、模板渲染、数据库操作等任务。
-
自动重新加载线程(Auto-reloader thread):这个线程主要负责监视项目中的源代码文件。当检测到文件发生更改时,它会自动重新加载项目,使得更改立即生效,无需手动重启服务器。这对于开发过程中的调试和快速迭代非常有帮助。
这样的设计可以使得开发者在开发过程中更加高效,因为当代码发生变化时,服务器会自动重新加载,而无需手动重启。同时,通过将自动重新加载功能放在一个单独的线程中,可以确保主线程始终专注于处理HTTP请求,提高服务器的响应速度。