进程--multiprocessing模块

本文深入讲解了Python中的并发编程概念,包括进程、线程的区别,以及如何使用multiprocessing模块进行多进程编程。此外,还介绍了进程间的通信方式、同步机制、进程池的应用,并对比了协程的优势。

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

进程–multiprocessing模块

一、知识梳理

1,进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

2,线程

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

3,并发与并行

无论是并行还是并发,在用户看来都是’同时’运行的,不管是进程还是线程,都只是一个任务而已,真正在干活的是cpu,cpu来做这些任务,而一个cpu(单核)同一时刻只能执行一个任务

并发:看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)
并行:任务同时在运行,只有cpu多核时才行

python的Gil特性下,根据任务种类的不同(io密集型、计算(cpu)密集型),选择使用多进程还是多线程。

4,同步与异步

同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。按照这个定义,其实绝大多数函数都是同步调用。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务

异步的概念和同步相对。当一个异步功能调用发出后,调用者不能立刻得到结果。当该异步功能完成后,通过状态、通知或回调来通知调用者。如果异步功能用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低。如果是使用通知的方式,效率则很高,因为异步功能几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。

二、Process类的介绍

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。

multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。

multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

group: 线程组,目前还没有实现,库引用中提示必须是None;
target: 要执行的方法;
name: 进程名;
args/kwargs: 要传入方法的参数。

实例方法:

  is_alive():返回进程是否在运行。

  join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。

  start():进程准备就绪,等待CPU调度

  run():strat()调用run方法,如果实例进程时未指定传入target,这start执行默认run()方法。

  terminate():不管任务是否完成,立即停止工作进程

属性:

  daemon:和线程的setDeamon功能一样

  name:进程名字。

  pid:进程号。

p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)  

三、Process使用

#直接使用Process类生成子进程
from multiprocessing import Process
import os,time

def test(num):
    print(num)
    print(time.ctime())
    print(os.getpid())

if __name__ == '__main__':
    # p1.start()
    p = []
    for i in range(10):
        p1 = Process(target=test,args=(i,))
        p.append(p1)
    print(p)
    for i in p:
        i.start()

    for i in p:
        i.join()
    print (time.ctime ())
    print (os.getpid ())
    print('the end')    
[<Process(Process-11, initial)>, <Process(Process-12, initial)>, <Process(Process-13, initial)>, <Process(Process-14, initial)>, <Process(Process-15, initial)>, <Process(Process-16, initial)>, <Process(Process-17, initial)>, <Process(Process-18, initial)>, <Process(Process-19, initial)>, <Process(Process-20, initial)>]
Thu May 24 21:24:41 2018
4440
the end
#自定义类继承Process类
from multiprocessing import Process
import os,time
class Myprocess(Process):
    def __init__(self,num):
        super().__init__()
        self.num = num

    def run(self):
        print(self.num)
        print(time.ctime())
        print(os.getpid())

if __name__ == '__main__':
    p = []
    for i in range(10):
        p1 = Myprocess(i)
        p.append(p1)
    print(p)


    for i in p:
        i.start()

    for i in p:
        i.join()
    print (time.ctime ())
    print (os.getpid ())
    print('the end')
[<Myprocess(Myprocess-1, initial)>, <Myprocess(Myprocess-2, initial)>, <Myprocess(Myprocess-3, initial)>, <Myprocess(Myprocess-4, initial)>, <Myprocess(Myprocess-5, initial)>, <Myprocess(Myprocess-6, initial)>, <Myprocess(Myprocess-7, initial)>, <Myprocess(Myprocess-8, initial)>, <Myprocess(Myprocess-9, initial)>, <Myprocess(Myprocess-10, initial)>]
Thu May 24 21:24:18 2018
4440
the end

四、进程间通信

1,进程队列–Queue

from multiprocessing import Process,Queue
import os,time

q = Queue()

def putit(q):
    q.put('123')
    q.put('abc')
    q.put({'ab':12})
    print(os.getpid())

def getit():
    print(q.get())
    print(q.get())
    print(q.get())
    print (os.getpid ())

if __name__ == '__main__':
    p = Process(target=putit,args=(q,))
    print(p.name,p.pid)
    p.start()
    p.join()

    getit()

2,管道–Pipe

from multiprocessing import Process, Pipe
def f(conn):
    conn.send('hello master')
    res = conn.recv()
    print('回复信息:',res)
    conn.close()
    print('q2_id:',id(conn))

if __name__ == "__main__":

    parent_conn,child_conn = Pipe()
    print('q1_id:',id(child_conn))
    p = Process(target=f,args=(child_conn,))
    p.start()
    print(parent_conn.recv())
    parent_conn.send('hello saber')

3,Manager

Queue和pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据。

from multiprocessing import Process, Manager

def f(d, l,n):

    d[n] = '1'    #{0:"1"}
    d['2'] = 2    #{0:"1","2":2}

    l.append(n)    #[0,1,2,3,4,   0,1,2,3,4,5,6,7,8,9]
    #print(l)


if __name__ == '__main__':

    with Manager() as manager:        #manager = Manager()

        d = manager.dict()#{}

        l = manager.list(range(5))#[0,1,2,3,4]


        p_list = []

        for i in range(10):
            p = Process(target=f, args=(d,l,i))
            p.start()
            p_list.append(p)

        for res in p_list:
            res.join()

        print(d)
        print(l)
{}
[0, 1, 2, 3, 4]

五、进程同步—同步锁

为了解决进程间互相抢占某一资源,比如输出,使用同步锁

from multiprocessing import Process,Lock
import time

def f(l,i):

    with l: 
        #l.aqquire()                 如果不用with
        print('hello %s'%i)
        time.sleep(1)
        #l.release()

if __name__ == '__main__':
    lock = Lock()

    for i in range(10):
        Process(target=f,args=(lock,i)).start()

六、进程池

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中有两个方法:

apply

apply_async

多进程是实现并发的手段之一,需要注意的问题是:

1、很明显需要并发执行的任务通常要远大于核数
2、一个操作系统不可能无限开启进程,通常有几个核就开几个进程
3、进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行)

例如当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个。。。手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

from  multiprocessing import Process,Pool
import time,os

def Foo(i):

    time.sleep(1)
    print(i)
    print("son",os.getpid())

    return "HELLO %s"%i


def Bar(arg):
    print(arg)
    # print("hello")
    # print("Bar:",os.getpid())

if __name__ == '__main__':

    pool = Pool(5)
    print("main pid",os.getpid())
    for i in range(100):
        #pool.apply(func=Foo, args=(i,))  #同步接口
        #pool.apply_async(func=Foo, args=(i,))

        #回调函数:  就是某个动作或者函数执行成功后再去执行的函数

        pool.apply_async(func=Foo, args=(i,),callback=Bar)

    pool.close()
    pool.join()         # join与close调用顺序是固定的

    print('end')

七、协程

协程,又称微线程,纤程。英文名Coroutine。

优点1: 协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

优点2: 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

#yield实现
import time

def consumer(name):
    print('-----ready to eat-----')
    while 1:
        new_sushi = yield
        print('[%s] is eating sushi %s'%(name,new_sushi))
        time.sleep(1)

def producer():

    r = cook.__next__()
    r = cook2.__next__ ()
    n = 0
    while 1:
        time.sleep(1)
        print('[producer] is making sushi %s and %s'%(n,n+1))
        cook.send(n)
        cook.send(n+1)

        n +=2

if __name__ == '__main__':
    cook = consumer('consumer1')
    cook2 = consumer('consumer2')
    p = producer()

greedlet模块

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator

from greenlet import greenlet

def test1():

    print(12)
    gr2.switch()
    print(34)
def test2():
    print(56)
    gr1.switch()
    print(78)
    gr1.switch()

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr2.switch()
Gevent模块
import gevent

import requests,time


start=time.time()

def f(url):
    print('GET: %s' % url)
    resp =requests.get(url)
    data = resp.text
    print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([

        gevent.spawn(f, 'https://www.python.org/'),
        gevent.spawn(f, 'https://www.yahoo.com/'),
        gevent.spawn(f, 'https://www.baidu.com/'),
        gevent.spawn(f, 'https://www.sina.com.cn/'),

])



print("cost time:",time.time()-start)
### 多进程分布式处理简介 在Python中,`multiprocessing`模块提供了创建和管理多个进程的能力。对于分布式处理而言,这意味着可以在不同的计算机上运行这些进程,并通过网络进行通信[^1]。 #### 使用Socket实现多进程间通信 一种常见的做法是利用套接字(Sockets),即在网络编程中的接口标准,用于不同主机上的应用程序之间的双向通信。具体来说,在一个典型的客户端-服务器架构里: - **服务器端**监听来自客户端的消息请求; - 当收到消息后执行相应的操作并将结果反馈给客户端; - 客户端负责向指定地址发起连接尝试并向服务端传递参数或指令。 这种模式非常适合于构建分布式的计算环境,其中某些节点作为“工作者”,专门用来完成特定的任务;而另一些则扮演协调者的角色——分发任务、收集成果等[^2]。 ```python import socket from multiprocessing import Process, Queue def server(q): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = '' port = 12345 s.bind((host, port)) while True: conn, addr = s.accept() data = conn.recv(1024).decode('utf8') q.put(data) response = "Received" conn.sendall(response.encode()) conn.close() if __name__ == '__main__': queue = Queue() p_server = Process(target=server, args=(queue,)) p_server.start() # 主进程中可以继续做其他事情... ``` 上述代码展示了简单的TCP服务器逻辑,它能够接收外部传入的数据并通过队列共享给主程序或其他子进程使用。这只是一个基础框架,实际应用时还需要考虑错误处理机制、并发性能优化等问题。 #### 进程之间数据交换的方式 除了基本的socket通信外,还可以采用更高级别的库如ZeroMQ、RabbitMQ来进行跨平台的消息传输和服务调用。另外,当涉及到复杂的业务场景比如机器学习模型训练时,则可能需要用到PyTorch DDP (Distributed Data Parallel) 或者 TensorFlow 的 Estimator API 来简化开发流程并提高效率[^3]。 例如,在深度学习领域内,可以通过如下方式设置一个多GPU/多机训练方案: ```python import torch.distributed as dist import torch.nn as nn import torchvision.models as models model = models.resnet50(pretrained=True) class ModelWrapper(nn.Module): def __init__(self, model): super().__init__() self.model = model def forward(self, x): output = self.model(x) return output.mean(dim=-1) wrapped_model = ModelWrapper(model) dist.init_process_group( backend='nccl', init_method="tcp://localhost:23456", rank=rank_id, world_size=num_workers ) torch.cuda.set_device(rank_id % num_gpus_per_node) ddp_model = nn.parallel.DistributedDataParallel(wrapped_model.to(f'cuda:{rank_id}')) ``` 这段代码片段说明了如何在一个由多个工作节点组成的集群环境中部署ResNet50神经网络模型,并让其支持大规模图像分类任务下的高效并行化运算过程。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值