多进程,多线程,协程

本文介绍了多进程、多线程和协程的基本概念。多进程通过 fork 系统调用创建子进程,常用于服务器处理新任务;多线程在 Python 中是 Posix Thread,但受限于 GIL,无法充分利用多核;协程作为微线程,高效执行,无线程切换开销。在 Linux 和 Mac 上可以使用模块进行多进程编程,Windows 平台则推荐使用模块。同时,文章强调了进程间通信和并发执行的区别,并指出在多核环境下,多进程优于多线程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本概念

多进程
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模块。
  • 进程间通信是通过QueuePipes等实现的。
  • 多线程能做到的,协程都可以做到.
  • 因为GIL的存在,多线程无法利用多核,要想利用多核只有使用多进程.
  • 当前面向多核的服务器端编程中,需要习惯使用多进程而非多线程。
  • 并行:多个CPU实例或多台机器同时执行一段处理逻辑,是真正的同时。
  • 并发:通过CPU调度算法,让用户看上去同时执行,实际上CPU操作层面不是真正的同时。
  • 多线程是异步的,但这不代表多线程真的是几个线程是在同时进行,实际上是系统不断地在各个线程之间来回的切换(因为系统切换的速度非常的快,所以给我们在同时运行的错觉)。

参考

教程1

教程2

教程3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值