43. 使用多进程

由于Python中全局解释器锁(GIL)的存在,在任意时刻只允许一个线程在解释器中运行,因此Python的多线程不适合处理CPU密集型的任务。

要求:想要处理CPU密集型的任务,可以使用多进程模型。

解决方案:

使用标准库中multiprocessing.Process类,它可以确定子进程执行任务。操作接口、进程间通信、进程加同步等都与threading.Thread类类似。


  • 对于multiprocessing.Process类:

在multiprocessing中,通过创建一个Process对象然后调用它的start()方法来生成进程。 Process和threading.Thread的API 相同,start()join()run()方法作用一致。

进程和线程的根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。

进程是线程的容器,不存在没有线程的进程。如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据。

  • 对于进程间通信:

队列:

线程间通信使用的是标准库中的queue.Queue类,它是一个线程安全的队列。而进程间通信使用的是multiprocessing.Queue类,它是一个近似queue.Queue的克隆,也是线程和进程安全的。

管道:

Pipe()函数返回一个由管道连接的连接对象,默认情况下是双工(双向)。返回的两个连接对象Pipe()表示管道的两端。每个连接对象都有send()recv()方法(相互之间的)。请注意,如果两个进程(或线程)同时尝试读取或写入管道的同一端,则管道中的数据可能会损坏。当然,同时使用管道的不同端的进程不存在损坏的风险。

>>> from multiprocessing import Process, Pipe

>>> c1, c2 = Pipe()

>>> def f(c):
...     print('in child')
...     data = c.recv()
...     print(data)
...     c.send(data * 2)
... 

>>> Process(target=f, args=(c2,)).start()
in child

>>> c1.send(100)
100

>>> c1.recv()
200

send()方法表示输入数据到管道;recv()方法表示从管道中接收数据。


  • 方案示例:
import time
from threading import Thread
from multiprocessing import Process
from queue import Queue as Thread_Queue
from multiprocessing import Queue as Process_Queue

def is_armstrong(n):                #判断是否是水仙花数
    a, t = [], n
    while t :
        a.append(t % 10)
        t //= 10
    k = len(a)
    return sum(x**k for x in a) == n

def find_armstrong(a, b, q=None):               #在(a, b)内找出水仙花数
    res = [x for x in range(a, b) if is_armstrong(x)]
    if q:
        q.put(res)
    return res

def find_by_thread(*ranges):                #通过线程寻找
    q = Thread_Queue()
    workers = []
    for r in ranges:
        a, b = r
        t = Thread(target=find_armstrong, args=(a, b, q))
        t.start()
        workers.append(t)
    
    res = []
    for _ in range(len(ranges)):
        res.append(q.get())
    return res

def find_by_process(*ranges):               #通过进程寻找
    q = Process_Queue()
    workers = []
    for r in ranges:
        a, b = r
        t = Process(target=find_armstrong, args=(a, b, q))
        t.start()
        workers.append(t)
    
    res = []
    for _ in range(len(ranges)):
        res.extend(q.get())
    return res

if __name__ == '__main__':
    t0 = time.time()
    res = find_by_thread([10000000, 15000000], [15000000, 20000000], [20000000, 25000000], [25000000, 30000000])
    # res = find_by_process([10000000, 15000000], [15000000, 20000000], [20000000, 25000000], [25000000, 30000000])
    print(res)
    print(time.time() - t0)
[[24678050, 24678051], [], [], []]              #find_by_thread 结果
98.16692352294922

[24678050, 24678051]                #find_by_process 结果
55.84143948554993

从运行结果来看,对于CPU密集型的任务,通过多进程来处理比多线程更好。


31. 在Linux系统下,发送信号的命令是kill。在C语言中,可以使用kill函数来发送信号。 32. pid_t fork() 的返回值为新进程进程ID,如果返回值为0,则表示当前进程为子进程;如果返回值大于0,则表示当前进程为父进程,返回值即为子进程进程ID;如果返回值为-1,则表示创建子进程失败。 33. 系统调用是通过操作系统提供的接口直接调用内核功能的一种编程方式,而库函数则是在用户空间中运行的函数库,通过调用库函数来实现对系统功能的访问。 34. C/S架构指的是客户端/服务器架构,其中客户端和服务器通过网络进行通信,客户端向服务器发起请求,服务器处理请求并返回结果给客户端。 35. sprintf函数是C语言中的字符串格式化函数,用于将格式化后的字符串输出到指定的缓冲区中。 36. 进程的创建可以通过fork函数来实现,进程间通信的机制包括管道、共享内存、消息队列、信号量等。 37. 线程的创建可以通过pthread_create函数来实现,线程间通信的机制包括互斥锁、条件变量、信号量等。 38. 如果a可能为负数,则应该定义为有符号类型。 39. 可以使用wait或waitpid函数来让父进程等待子进程结束并获取子进程的退出状态。 40. 使用管道和共享内存时,需要先创建相应的管道或共享内存,然后通过读写操作来进行进程间通信。 41. 进程关闭后,管道或共享内存仍然存在,但是不能再进行读写操作。 42. 僵尸进程是指已经结束但是父进程还没有处理其退出状态的进程。在父进程没有使用wait或waitpid等函数来处理子进程退出状态时,子进程就会变成僵尸进程43. 解决僵尸进程的方法包括使用wait或waitpid函数等待子进程退出并获取其退出状态,或者使用信号处理函数来处理SIGCHLD信号。 44. 进程是系统中正在运行的程序的实例,拥有独立的内存空间和资源。线程是进程中的执行单元,多个线程共享同一进程的资源,可以提高程序的并发性和效率。 45. 进程间的通信方式包括管道、共享内存、消息队列、信号量等。 46. 同步是指协调多个进程或线程的执行顺序,保证它们按照一定的顺序执行。互斥是指在同一时间只有一个进程或线程可以访问共享资源,其他进程或线程需要等待该进程或线程释放资源后才能访问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值