基本概念
多进程
unix/linux操作系统提供一个fork()
系统调用,调用一次,返回两次,在调用时操作系统自动把当前进程(父进程)复制了一份(子进程),然后分别在父进程和子进程内返回.
- 子进程返回0,父进程返回子进程id.
- 子进程调用getppid()可以拿到父进程的ID。
- 多进程的主进程一定要写在程序入口
if __name__ =='__main__':
内部
多线程
线程是操作系统直接支持的执行单元,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。
协程
又称为微线程,在一个线程中执行,执行函数时可以随时中断,由程序(用户)自身控制,执行效率极高,与多线程比较,没有切换线程的开销和多线程锁机制。
多进程示例
import os
print("process (%s) start ..." % os.getpid())
pid = os.fork()
if pid == 0 :
print('this is child processs: %s and paraent is %s' % (os.getpid(),os.getppid()))
else:
print("this is %s and created child is : %s" % (os.getpid(),pid))
---执行结果:
process (38030) start ...
this is 38030 and created child is : 38031
this is child processs: 38031 and paraent is 38030
有了fork调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。
fork()无法在windows平台调用,可以使用multiprocessing
模块.
import os
from multiprocessing import Process
def run_pro(name):
print("run child process %s (%s) ..." % (name,os.getpid()))
if __name__ == '__main__':
print('parent process %s.' % os.getpid())
p = Process(target=run_pro,args=('test',)) # 创建子进程
print('child process will start.')
p.start() # 启动子进程
p.join() # 等待子进程结束后再往下运行,通常用语进程间的同步
print('child process end.')
--执行结果:
parent process 38206.
child process will start.
run child process test (38207) ...
child process end.
进程池示例
import os , time ,random
from multiprocessing import Pool
def long_time_task(name):
print('Run task %s (%s) ...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds. ' % (name,(end-start)))
if __name__ == '__main__':
print('parent process %s.' % os.getpid())
p = Pool(4) # 设置最多同时执行4个进程。
for i in range(5):
p.apply_async(long_time_task,args=(i,))
print("wating for all subprocesses done...")
p.close() # 调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
p.join() # 对Pool对象调用join()方法会等待所有子进程执行完毕,
print("All subprocessed done.")
--执行结果:
parent process 38542.
wating for all subprocesses done...
Run task 0 (38543) ...
Run task 1 (38544) ...
Run task 2 (38545) ...
Run task 3 (38546) ...
Task 2 runs 0.68 seconds.
Run task 4 (38545) ...
Task 0 runs 1.62 seconds.
Task 1 runs 1.66 seconds.
Task 3 runs 2.69 seconds.
Task 4 runs 2.71 seconds.
All subprocessed done.
进程间通信 - Queue
,Pipes
import os , time ,random
from multiprocessing import Process,Queue
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A','B','C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value=q.get(True)
print('Get %s from queue.' % value)
if __name__ == '__main__':
q = Queue() # 父进程创建Queue并传给各个子进程
pw = Process(target=write,args=(q,))
pr = Process(target=read,args=(q,))
pw.start()
pr.start()
pw.join() # 等待pw结束
pr.terminate() # pr进程里是死循环,无法等待结束,只能强行终止
---运行结果:
Process to write: 38781
Put A to queue...
Process to read: 38782
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.
协程示例
import time
import asyncio
# async修饰声明异步函数
# 调用异步函数,得到协程对象(coroutine object),并不会真正执行这个函数
async def crawl_page(url):
print('crawling {}'.format(url))
sleep_time=int(url.split('_')[-1])
await asyncio.sleep(sleep_time) #await是同步调用,通过await调用的话,程序会阻塞在这里,进入被调用的协程函数, 执行完毕返回后再继续
print('OK {} '.format(url))
# python3.7以后支持下面两个方法:
# asyncio.create_task() 创建任务
# asyncio.run() 触发运行 ; 编程规范是,asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次 asyncio.run
async def main(urls):
#for url in urls:
# await crawl_page(url)
tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
for task in tasks:
await task
asyncio.run(main(['url_1','url_2','url_3','url_4']))
Tips
- mac/linux可以用
os
模块的fork()
实现多进程,涉及windows跨平台则可以用multiprocessing
模块。 - 进程间通信是通过
Queue
、Pipes
等实现的。 - 多线程能做到的,协程都可以做到.
- 因为GIL的存在,多线程无法利用多核,要想利用多核只有使用多进程.
- 当前面向多核的服务器端编程中,需要习惯使用多进程而非多线程。
- 并行:多个CPU实例或多台机器同时执行一段处理逻辑,是真正的同时。
- 并发:通过CPU调度算法,让用户看上去同时执行,实际上CPU操作层面不是真正的同时。
- 多线程是异步的,但这不代表多线程真的是几个线程是在同时进行,实际上是系统不断地在各个线程之间来回的切换(因为系统切换的速度非常的快,所以给我们在同时运行的错觉)。