Python:多进程。
参考:https://www.liaoxuefeng.com/wiki/1016959663602400/1017628290184064
Python程序实现多进程(multiprocessing)
- Fork系统和Ruby相关
- Python多进程
- os模块
- multiprocessing模块:Process类,Pool类
- 子进程subprocess模块
- multiprocessing中的Pipe()和Queue(), 以及Connection对象。
了解linux的Fork系统调用(wiki)
在计算机领域中,尤其是Unix及类Unix系统操作系统中,fork(进程复制)是一种创建自身行程副本的操作。它通常是内核实现的一种系统调用。Fork是类Unix操作系统上创建进程的一种主要方法,甚至历史上是唯一方法。
在多任务操作系统中,行程(运行的程序)需要一种方法来创建新进程,例如运行其他程序。
如果进程需要启动另一个程序的可执行文件,它需要先Fork来创建一个自身的副本。然后由该副本即“子进程”调用exec系统调用,用其他程序覆盖自身:停止执行自己之前的程序并执行其他程序。
当一个进程调用fork时,它被认为是父进程,新创建的进程是它的孩子(子进程)。在fork之后,两个进程不仅运行着相同的程序,并且它们恢复执行(好像它们都已被系统调用)both processes not only run the same program, but they resume execution as though both had called the system call. 。然后它们可以检查调用的返回值确定其状态:是父进程还是子进程,以及据此行事。
Ruby的多进程处理模块Process
Ruby核心模块Process提供了大量和unix对应的接口方法。
fork[{block}] -> integer or nil
创建子进程。如果提供了block则会在子进程内运行。子进程终止会返回一个状态码0。
调用fork会返回两次,一次是父进程,它返回孩子进程的ID; 另一次是孩子进程,返回nil。
孩子进程使用Kernel.exit!来退出。
父亲进程需要使用Process.wait来收集孩子进程的终止状态码。
exec([env,] command...[,options])
子进程调用exec方法,通过运行参数的命令取代当前进程。
pid -> integer
返回当前进程的id. Process.pid => 37415
ppid -> integer
返回当前进程的父进程的id.
- puts "#{Process.pid}" Process.fork { puts "child_id:#{Process.pid}\nfather_id:#{Process.ppid}"}
- 第一行代码输出当前进程的id
- 第二行代码使用fork带一个块,块在子进程内运行,输出子进程自身的id, 和父进程的id,使用ppid方法,即parent process id。
多线程教程:https://www.runoob.com/ruby/ruby-multithreading.html
Python多进程
OS模块
Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程。
fork函数,调用一次,返回2次:
分别在子进程返回0,在父进程返回子进程的ID,这样设计的原因是:
- 一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,
- 而子进程只需要调用
getppid()就可以拿到父进程的ID
import os print("current process %s" % os.getpid()) #输出当前进程的🆔 pid = os.fork() #复制出一个子进程。pid指向返回值。每个进程各自有不同的pid。 print("》current process %s" % os.getpid()) #在每个进程内打印它的🆔。 if pid == 0: #根据fork返回值来判断,当前在哪个进程的内部! print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid())) else: print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
- os.getpid()返回当前进程id
- os.getppid()返回当前进程的父进程的id。
#一次输出结果: current process 36212 》》current process 36212 #这行和下一行,是在主进程内执行后,打印出的。 I (36212) just created a child process (36213). 》》current process 36213 #最后2行输出,是在子进程内执行后,打印的。 I am child process (36213) and my parent is 36212.
解释:
首先,打印出当前进程的id.
其次,fork当前进程,产生一个子进程。然后这个fork函数在父进程内返回子进程的🆔, 并在子进程的内部返回状态码0。
之后,在每个进程内,打印自身的🆔。
最后,一个if..else..语句的判断。这是因为是2个进程,每个进程都有if语句。根据fork函数的返回值,可以判断当前进程的位置是父进程还是子进程。然后执行不同的代码。
⚠️:Windows没有fork调用。
fork的实际用途
当一个进程在接到新的任务时,可以通过fork函数来复制一个子进程,用子进程来处理新任务。例子:
常见的Apache服务器,由父进程监听端口port, 每当有新的http请求时,就fork一个子进程,用来处理新的http请求。
multiprocessing--Process-based parallelism
这个模块支持Win和Unix系统。所以可以用它在window系统上编写多进程服务程序。
根据操作系统不同,这个模块支持3种启动进程的方法:
- spawn(3.4版本增加的,速度比fork和forkserver慢,但自3.8后对于macOS, 这是默认方式,因为安全问题)。父进程启动一个新的Python解释器进程。子进程只继承必要的资源,运行进程对象的run()方法所必须的资源。
- fork, 这种启动方式只存在于Unix系统内,是默认的。使用os.fork()来产生Python解释器分叉。
- forkserver
Process类
模块内的一个类,用于创建一个Process对象(代表一个进程对象)。然后调用start()方法启动子进程。
from multiprocessing import Process import os # 子进程要执行的代码 def run_proc(name): print('Run child process %s (%s)' %(name, os.getpid()) ) if __name__ == '__main__': print('Parent process %s.' % os.getpid() ) p = Process(target=run_proc, args=('test', )) p.start() p.join() print('Child process end.')
注意:
创建Process类的对象时,要写2个参数:
- target指向创建的子进程内运行的代码。
- args是传入子进程内部的参数。
join([timeout])
方法可以等待调用他的子进程结束后,继续之后的代码运行,通常用于进程间的同步。
- 如果可选参数 timeout 是
None(默认值),则该方法将阻塞,直到调用join()方法的进程终止。 - 如果 timeout 是一个正数,它最多会阻塞 timeout 秒。
⚠️运行上面代码遇到一个错误,TypeError: run_proc() takes 1 positional argument but 5 were given
原因, args是一个'test,'字符串,传入的参数args应该是tuple,即('test', )。输入错误。
p = Process(target=run_proc, args=('test,'))
Pool类:
如果要启动大量的子进程,可以用进程池的方式批量创建子进程。
Pool会赋予函数并行化处理一系列输入值的能力,可以将输入的数据分配给不同的进程处理(数据并行)。
⚠️Pool的含义是从池水引申为是备用的共用资源
简单例子:
from multiprocessing import Pool def f(x): return x*x if __name__ == '__main__': with Pool(5) as p: print(p.map(f, [1,2,3])) #[1, 4, 9]
解释:
map(fun, iterable[, chunksize])
内置函数map的并行版本。它是multiprocessing包内的方法。它在结果准备好前,会阻塞block。
map_async(func, iterable[, chunksize[, callback[, error_callback]]])
和上面的map()方法类似,可以通过callback返回一个可调用对象。
with声明:(点击查看原理)
封装了启动和关闭子进程的功能,可以自动关闭文件、线程锁的自动获取和释放等。
Using a pool of workers(work processes) 使用备用的工作进程。ma p
附录:pool原意是水池,引申为备用的人员:a pool of cheap labour ,廉价劳动力。所以文档中a pool of workers翻译过来是备用的进程。
Pool类代表了备用的工作进程。它的实例方法可以把任务卸载offload到这些备用进程内。
from multiprocessing import Pool import os, time, random def long_time_task(name): print("Run task %s (%s)..." %(name, os.getpid())) start = time.time() #获得1970年到现在的秒数。 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) for i in range(5): p.apply_async(long_time_task, args=(i,)) print('Waiting for all subprocesses done...') p.close() p.join() print('All subprocesses done.')
结果:
Run task 0 (36505)... Run task 1 (36503)... Run task 2 (36506)... Run task 3 (36504)... Task 1 runs 1.15 seconds. Run task 4 (36503)... Task 2 runs 1.74 seconds. Task 0 runs 2.03 seconds. Task 3 runs 2.50 seconds. Task 4 runs 1.45 seconds. All subprocesses done.
解释:
Pool的apply_async(func[, args[, kwds[, callback[, error_callback]]]])
apply方法的并行版本,返回一个结果对象。在返回结果前会block阻塞。 ⚠️类似map和map_async。
Pool的close()方法:
阻止后续任务提交到进程池,当所有任务执行完成后,工作进程会退出。
Pool的join([timeout])
必须在close()或terminate()后调用。等待工作进程结束。
子进程--subprocess模块
很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。
subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
进程间通信 (点击查看官方文档的例子)
Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。
Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes在进程之间交换object。
class multiprocessing.Queue --队列
Queue类其实就是对模块queue中的Queue类的克隆。使用方法大致一样。
get([block[, timeout]])
移除并返回一个item,如果block是True, timeout是None,即都是默认,那么会阻塞,直到一个item可以被使用。
如果timeout存在,则最多阻塞timeout秒,如果没有item可用,raise queue.Empty例外。
注意⚠️,队列其实在底层使用了管道机制。Queue()返回一个使用一个管道和少量锁和信号量实现的共享队列实例。当一个进程将一个对象放进队列中时,一个写入线程会启动并将对象从缓冲区写入管道中。
例子:
from multiprocessing import Process, Queue import os, time, random def write(arg): print('Process to write: %s' % os.getpid()) for value in ['A', 'B', 'C']: print('Put %s to queue...' % value) arg.put(value) time.sleep(random.random()) def read(arg): print('Process to read: %s' % os.getpid()) while True: value = arg.get() print('Get %s from queue' % value) if __name__ == '__main__': q = Queue() pw = Process(target=write, args=(q,)) pr = Process(target=read, args=(q,)) pw.start() #启动子进程pw,写入 pr.start() #启动子进程pr,读取 pw.join() #等待pr结束 pr.terminate() #pr是死循环,无法等待结束,强行终止。
multiprocessing.Pipes()函数
返回一对相互连接的Connection对象。表示管道的两端,默认True是双向连接。如果是False,则是单向传输数据。
class multiprocessing.connection.Connection对象
它用于发送/接收管道对象或strings。使用Pipe()创建。
- send(obj)
- recv()
- close()关闭这个连接。这个方法会被垃圾收集机制自动调用
⚠️:
例子:
from multiprocessing import Process, Pipe def f(conn): conn.send([42, None, 'hi']) conn.close() if __name__ == '__main__': parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() print(parent_conn.recv()) p.join()
小结:
使用多进程时,一般使用“信息传递机制”实现进程之间的通信。避免使用任何原始的同步方式(同步原语)如locks。
“信息传递机制”:
- Pipe(), 连接2个进程,
- 一个队列(能够在多个producers和contumers之间通信)
浙公网安备 33010602011771号