小白多进程之路
进程(Process)是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。
对于计算密集型任务来说,Python 的多进程相比多线程,其多核运行效率会有成倍的提升。
# 创建 Process 来新建一个子进程,其中 target 参数传入方法名,args 是方法的参数,是以元组的形式传入
import multiprocessing def process(index):
print(f'Process: {index}')
if __name__ == '__main__': for i in range(5): p = multiprocessing.Process(target=process, args=(i,))
p.start()
- 这里 args 必须要是一个元组,如果只有一个参数,那也要在元组第一个元素后面加一个逗号,如果没有逗号则和单个元素本身没有区别,无法构成元组,导致参数传递出现问题。
- 由于时python最小的分配单位是进程,各个进程之间的数据是不会共享的,每启动一个进程,都会独立分配资源。
CPU 的核心数量
import multiprocessing import time def process(index): time.sleep(index) print(f'Process: {index}') if __name__ == '__main__': for i in range(5): p = multiprocessing.Process(target=process, args=[i]) p.start() print(f'CPU number: {multiprocessing.cpu_count()}') # CPU线程数 for p in multiprocessing.active_children(): # active_children 获取到了当前正在活跃运行的进程列表 print(f'Child process name: {p.name} id: {p.pid}') # 进程号直接使用 pid 属性即可获取,进程名称直接通过 name 属性即可获取。 print('Process Ended')
继承 Process 类
class MyProcess(Process): def __init__(self, loop): Process.__init__(self) self.loop = loop # 循环次数 def run(self): for count in range(self.loop): time.sleep(1) print(f'Pid: {self.pid} LoopCount: {count}') # 循环次数 # 可以看到三个进程,分别使用2,3,4 if __name__ == '__main__': for i in range(2, 5): p = MyProcess(i) # 这里进程的执行逻辑需要在 run 方法中实现,启动进程需要调用 start 方法,调用之后 run 方法便会执行。 p.start()
- 继承父类Process,这里不能用函数的方式来理解函数,可以用多进程的角度,调用多进程,第一个进程循环两次,第二个进程循环三次
守护进程
from multiprocessing import Process import time # 如果一个进程为守护进程,当父进程结束后,子进程会自动被终止,设置 daemon 属性是否为守护进程 class MyProcess(Process): def __init__(self, loop): Process.__init__(self) self.loop = loop def run(self): for count in range(self.loop): time.sleep(1) print(f'Pid: {self.pid} LoopCount: {count}') if __name__ == '__main__': for i in range(2, 5): p = MyProcess(i) p.daemon = True # 守护进程,因为主进程没有做任何事情,直接输出一句话结束,所以在这时也直接终止了子进程的运行。 p.start() # 这样可以有效防止无控制地生成子进程。这样的写法可以让我们在主进程运行结束后无需额外担心子进程是否关闭,避免了独立子进程的运行。 print('Main Process ended')
但问题子进程还未运行,主程序执行退出,子进程(守护进程)也都退出了。
processes = [] for i in range(3, 5): p = MyProcess(i) processes.append(p) p.daemon = True p.start() for p in processes: p.join(1)
-
p.join() # 在调用 start 和 join 方法后,父进程就可以等待所有子进程都执行完毕后,再打印出结束的结果。
-
如果子进程死循环,父进程一直等待,可以给 join 方法传递一个超时参数,代表最长等待秒数。如果子进程没有在这个指定秒数之内完成,会被强制返回。
终止进程
import multiprocessing import time def process(): print('Starting') time.sleep(5) print('Finished') if __name__ == '__main__': p = multiprocessing.Process(target=process) print('Before:', p, p.is_alive()) p.start() # 启动 print('During:', p, p.is_alive()) p.terminate() # 终止 print('Terminate:', p, p.is_alive()) p.join() # 调用 join 方法可以为进程提供时间来更新对象状态,用来反映出最终的进程终止效果。 print('Joined:', p, p.is_alive()) # 通过 is_alive 方法判断当前进程是否还在运行。
互斥锁
问题:两个进程同时输出,结果第一个进程的换行没有来得及输出,第二个进程就输出了结果,导致最终输出没有换行。
- 采用互斥锁,多个进程运行期间的任一时间,只能一个进程输出,其他进程等待
from multiprocessing import Process, Lock import time # 进程互斥锁可以使同一时刻只有一个进程能访问共享资源 class MyProcess(Process): def __init__(self, loop, lock): Process.__init__(self) self.loop = loop self.lock = lock def run(self): for count in range(self.loop): time.sleep(0.1) self.lock.acquire() # 锁接收 print(f'Pid: {self.pid} LoopCount: {count}') self.lock.release() # 锁释放 if __name__ == '__main__': lock = Lock() # 互斥对象 for i in range(10, 15): p = MyProcess(i, lock) p.start()
信号量
-
经典生产者消费者
- 实现两进程的同步关系,是在其一个进程中执行p,另一个进程中实行v
from multiprocessing import Process, Semaphore, Lock, Queue import time # 生产者消费者共享一个初始化为空、大小为n的缓冲区,由于缓冲区是临界资源,需要互斥,使用互斥锁 buffer = Queue(10) empty = Semaphore(1) # 一个代表缓冲区空余数,同步信号量 full = Semaphore(0) # 一个表示缓冲区占用数 lock = Lock() class Producer(Process): def run(self): global buffer, empty, full, lock while True: empty.acquire() # 获取缓冲区空余数,消耗一个缓冲区空区 lock.acquire() # 进程互斥锁 buffer.put(1) # 队列加一 print('Producer append an element') time.sleep(1) lock.release() full.acquire() # 缓冲区释放 # 经典生产者消费者 class Consumer(Process): def run(self): global buffer, empty, full, lock while True: full.release() # 消耗一个产品 lock.acquire() # 进程互斥锁 buffer.get() # 获取队列 print('Consumer pop an element') time.sleep(1) lock.release() # 释放 empty.release() # 缓冲区空余处释放一个 if __name__ == '__main__': p = Producer() c = Consumer() p.daemon = c.daemon = True p.start() c.start() p.join() c.join() print('Main Process Ended') # 主要理解的具体意思
管道
-
实现进程间通信
from multiprocessing import Process, Pipe class Consumer(Process): def __init__(self, pipe): Process.__init__(self) self.pipe = pipe def run(self): self.pipe.send('Consumer Words') print(f'Consumer Received: {self.pipe.recv()}') # 通过管道接受进程的消息 class Producer(Process): def __init__(self, pipe): Process.__init__(self) self.pipe = pipe def run(self): print(f'Producer Received: {self.pipe.recv()}') self.pipe.send('Producer Words') if __name__ == '__main__': pipe = Pipe() p = Producer(pipe[0]) c = Consumer(pipe[1]) p.daemon = c.daemon = True p.start() c.start() p.join() c.join() print('Main Process Ended'
进程池
-
# 假如现在我们遇到这么一个问题,我有 10000 个任务,每个任务需要启动一个进程来执行,并且一个进程运行完毕之后要紧接着启动下一个进程,
# 同时我还需要控制进程的并发数量,不能并发太高,不然 CPU 处理不过来(如果同时运行的进程能维持在一个最高恒定值当然利用率是最高的
from multiprocessing import Pool import time def function(index): print(f'Start process: {index}') time.sleep(3) print(f'End process {index}', ) if __name__ == '__main__': pool = Pool(processes=3) # 进程池为3,如果不指定,自动根据处理器内核来分配进程数 for i in range(4): pool.apply_async(function, args=(i,)) # apply_async 方法 print('Main Process started') pool.close() pool.join() print('Main Process ended') # 进程池大小为 3,所以最初可以看到有 3 个进程同时执行,第4个进程在等待,在有进程运行完毕之后,第4个进程马上跟着运行,出现了如上的运行效果
map使用
from multiprocessing import Pool import urllib.request import urllib.error def scrape(url): try: urllib.request.urlopen(url) print(f'URL {url} Scraped') except (urllib.error.HTTPError, urllib.error.URLError): print(f'URL {url} not Scraped') if __name__ == '__main__': pool = Pool(processes=4) urls = [ 'https://www.baidu.com', 'http://www.meituan.com/', 'http://blog.csdn.net/', 'http://xxxyxxx.net' ] pool.map(scrape, urls) pool.close() # 这样,我们就可以实现 4 个进程并行运行。不同的进程相互独立地输出了对应的爬取结果