python 多进程和多线程编程

本文详细介绍了Python的多进程编程,包括多进程模块的引入背景、Process类的使用方法、进程池Pool的应用技巧,以及多函数进程和常用注意事项。通过实例演示了不同场景下的多进程编程实践。

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

1. 简介

多进程(multiprocessing)模块是在 Python 2.6 版本中加入的,和多线程(threading)模块类似,都是用来做并行运算的。不过Python既然有了threading,为什么还要搞一个multiprocessing呢?这是因为Python内部有一个全局解释锁(GIL),任何一个进程任何时候只允许一个线程进行CPU运算,如果一个进程中的某个线程在进行CPU运算时获得GIL,其他线程将无法进行CPU运算只能等待,使得多线程无法利用CPU多核的特性。多进程处理实际上对每个任务都会生成一个操作系统的进程,并且每一个进程都被单独赋予了Python的解释器和GIL,所以程序在运行中有多个GIL存在,每个运行者的线程都会拿到一个GIL,在不同的环境下运行,自然也可以被分配到不同的处理器上。

python的多线程适用于IO密集型任务,IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成

多进程适用于CPU密集型任务,也称为计算密集型任务,计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数

参考什么是CPU密集型、IO密集型?

2. 多进程编程

2.1 Process类

Process(group, target, name, args, kwargs)
target: 表示调用对象(一个函数,用于处理任务)
args:表示调用对象的位置参数元组
kwargs:表示调用对象的字典
name:为别名
group:实质上不使用

方法
start(): Process以start()启动某个进程
join() :主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用
close() : 关闭pool,使其不在接受新的任务
terminate() : 结束工作进程,不在处理未完成的任务

属性
name:进程名
pid:进程号
daemon:当设置p.daemon = True时,不管子进程有没有运行结束,当主进程结束后子进程都结束;具体看例4

例1. 入门: 创建函数并将其作为单个进程
import multiprocessing
import time

def worker(interval):
    n = 2
    while n > 0:
        print("The time is {0}".format(time.ctime()))
        time.sleep(interval)
        n -= 1

if __name__ == "__main__":
    print("current time:", time.ctime())
    # 创建一个进程, 一般设置参数target和args即可
    p = multiprocessing.Process(target = worker, args = (3,), name = "worker")
    # 启动进程
    p.start()
    # 打印进程号
    print("p.pid:", p.pid)
    # 打印进程名
    print("p.name:", p.name)
    # 进程是否正在运行
    print("p.is_alive:", p.is_alive())
    time.sleep(10)
    # 10秒后进程已经结束
    print("p.is_alive:", p.is_alive())

输出

current time: Sat May 30 15:16:04 2020
p.pid: 10692
p.name: worker
p.is_alive: True
The time is Sat May 30 15:16:05 2020
The time is Sat May 30 15:16:08 2020
p.is_alive: False
例2:创建函数并将其作为多个进程
import multiprocessing
import time
def worker_1(interval):
    print("worker_1, start time: ", time.ctime())
    time.sleep(interval)
    print("end worker_1, end time: ", time.ctime())

def worker_2(interval):
    print("worker_2, start time: ", time.ctime())
    time.sleep(interval)
    print("end worker_2, end time: ", time.ctime())

if __name__ == "__main__":
    p1 = multiprocessing.Process(target = worker_1, args = (2,))
    p2 = multiprocessing.Process(target = worker_2, args = (3,))
    p1.start()
    p2.start()
    # 打印cpu核心数
    print("The number of CPU is:" + str(multiprocessing.cpu_count()))
    # 遍历每一个运行中的子进程
    for p in multiprocessing.active_children():
        print("child   p.name:" + p.name + "\tp.id" + str(p.pid))
    print("END!!!!!!!!!!!!!!!!!")

输出

The number of CPU is:8
child   p.name:Process-2	p.id3568
child   p.name:Process-1	p.id7968
END!!!!!!!!!!!!!!!!!
worker_1, start time:  Sat May 30 15:24:44 2020
end worker_1, end time:  Sat May 30 15:24:46 2020
worker_2, start time:  Sat May 30 15:24:44 2020
end worker_2, end time:  Sat May 30 15:24:47 2020
[Finished in 3.3s]
例3:将进程定义成类
import multiprocessing
import time
def worker_1(interval):
    print("worker_1, start time: ", time.ctime())
    time.sleep(interval)
    print("end worker_1, end time: ", time.ctime())

def worker_2(interval):
    print("worker_2, start time: ", time.ctime())
    time.sleep(interval)
    print("end worker_2, end time: ", time.ctime())
# 定义一个进程
class ClockProcess1(multiprocessing.Process):
    def __init__(self, interval):
        super(ClockProcess1,self).__init__()
        self.interval = interval

    def run(self):
    #可以直接调用函数,也可以将函数体直接放在run方法里
        worker_1(self.interval)
#定义第二个进程
class ClockProcess2(multiprocessing.Process):
    def __init__(self, interval):
        super(ClockProcess2,self).__init__()
        self.interval = interval

    def run(self):
        worker_2(self.interval)
# 主进程
if __name__ == '__main__':
    p1 = ClockProcess1(3)
    p2 = ClockProcess2(3)
    p1.start()
    p2.start()

输出

worker_1, start time:  Sat May 30 15:44:33 2020
end worker_1, end time:  Sat May 30 15:44:36 2020
worker_2, start time:  Sat May 30 15:44:33 2020
end worker_2, end time:  Sat May 30 15:44:36 2020

注意:进程p调用start()时,自动调用run()

例4:daemon属性设置后的区别

(1)不设置daemon属性

import multiprocessing
import time

def worker(interval):
    print("work start:{0}".format(time.ctime()));
    time.sleep(interval)
    print("work end:{0}".format(time.ctime()));

# 主进程
if __name__ == "__main__":
    p = multiprocessing.Process(target = worker, args = (3,))
    p.start()
    print("end!", time.ctime())

输出

end! Sat May 30 16:06:48 2020
work start:Sat May 30 16:06:48 2020
work end:Sat May 30 16:06:51 2020

可以发现不设置daemon属性,主进程结束后子进程还在继续执行

(2)设置daemon属性

import multiprocessing
import time

def worker(interval):
    print("work start:{0}".format(time.ctime()));
    time.sleep(interval)
    print("work end:{0}".format(time.ctime()));

# 主进程
if __name__ == "__main__":
    p = multiprocessing.Process(target = worker, args = (3,))
    p.daemon = True
    p.start()
    time.sleep(2)
    print("end!", time.ctime())

输出

end! Sat May 30 16:10:47 2020

可以看到,主进程结束后,尽管子进程还没结束,整个程序也就结束
注意:子进程程序中会等待子进程所有程序都执行完毕再一起输出,因此上面例子中子进程不会先打印一个时间。

可以使用join()方法等待子进程结束,如下

import multiprocessing
import time

def worker(interval):
    print("work start:{0}".format(time.ctime()));
    time.sleep(interval)
    print("work end:{0}".format(time.ctime()));

# 主进程
if __name__ == "__main__":
    p = multiprocessing.Process(target = worker, args = (3,))
    p.daemon = True
    p.start()
    # 等待子进程结束
    p.join()
    print("end!", time.ctime())

输出

ork start:Sat May 30 16:14:24 2020
work end:Sat May 30 16:14:27 2020
end! Sat May 30 16:14:27 2020

2.2 进程池Pool

在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

  • 使用multiprocessing.Pool()来创建进程池,可以设置进程池的大小,默认为CPU的核心个数,一般进程也不会超过CPU的核心个数,超过了效率也不会高
  • 进程池可以通过mapapply_async方法来调用执行代码,传入之后,就进入了子进程等待队列中,等待执行。返回值是ApplyResult对象,存储子进程的结果
  • ApplyResult对象的get()方法,获取子进程的返回值
  • join()方法等待所有子进程的结束,调用前必须使用close()方法
例5. 使用进程池(apply_async&apply)

(1)apply_async(非阻塞)

import multiprocessing
import time

def func(msg, i):
    print("msg:", msg, time.ctime())
    time.sleep(3)
    print("%d end"%i, time.ctime())

if __name__ == "__main__":
	#维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
    pool = multiprocessing.Pool(processes = 3)
    for i in range(2):
        msg = "hello %d" %(i)
        pool.apply_async(func, (msg, i))   

    print("Mark~ Mark~ Mark", time.ctime())
    pool.close()
    #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    pool.join()   
    print("Sub-process(es) done.", time.ctime())

输出

Mark~ Mark~ Mark Sat May 30 17:16:34 2020
msg: hello 0 Sat May 30 17:16:34 2020
0 end Sat May 30 17:16:37 2020
msg: hello 1 Sat May 30 17:16:34 2020
1 end Sat May 30 17:16:37 2020
Sub-process(es) done. Sat May 30 17:16:37 2020

(2) apply(阻塞)

import multiprocessing
import time

def func(msg, i):
    print("msg:", msg, time.ctime())
    time.sleep(3)
    print("%d end"%i, time.ctime())

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes = 3)
    for i in range(2):
        msg = "hello %d" %(i)
        pool.apply(func, (msg, i))   

    print("Mark~ Mark~ Mark", time.ctime())
    pool.close()
    pool.join()   
    print("Sub-process(es) done.", time.ctime())

输出

Mark~ Mark~ Mark Sat May 30 17:20:49 2020

msg: hello 0 Sat May 30 17:20:43 2020
0 end Sat May 30 17:20:46 2020

msg: hello 1 Sat May 30 17:20:46 2020
1 end Sat May 30 17:20:49 2020
Sub-process(es) done. Sat May 30 17:20:49 2020

从上面两个例子可以看出async_apply,为非阻塞形式,子进程同时执行,而apply为阻塞形式,子进程是串联执行,即一个执行完再执行另一个子程序。apply_async_apply和apply传入的参数第一个为fun, 第二个是fun的参数,以元组形式传入。

例6. 使用进程池并关注结果
import multiprocessing
import time

def func(msg):
    print("msg:", msg, "\t", time.ctime())
    time.sleep(3)
    print("end\t", time.ctime())
    return "done" + msg+"\t"+str(time.ctime())

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes=4)
    result = []
    for i in range(3):
        msg = "hello %d" %(i)
        result.append(pool.apply_async(func, (msg, )))
    pool.close()
    pool.join()
    for res in result:
        print(":::", res.get())
    print("Sub-process(es) done.")

输出

msg: hello 0 	 Sat May 30 17:41:24 2020
end	 Sat May 30 17:41:27 2020
msg: hello 1 	 Sat May 30 17:41:24 2020
end	 Sat May 30 17:41:27 2020
msg: hello 2 	 Sat May 30 17:41:24 2020
end	 Sat May 30 17:41:27 2020
::: donehello 0	Sat May 30 17:41:27 2020
::: donehello 1	Sat May 30 17:41:27 2020
::: donehello 2	Sat May 30 17:41:27 2020
Sub-process(es) done.
例7.多函数进程
import multiprocessing
import os, time, random

def Lee():
    print ("\nRun task Lee-%s" %(os.getpid())+"\t", time.ctime()) #os.getpid()获取当前的进程的ID
    start = time.time()
    time.sleep(random.random() * 10) #random.random()随机生成0-1之间的小数
    end = time.time()
    print ('Task Lee, runs %0.2f seconds.' %(end - start))

def Marlon():
    print ("\nRun task Marlon-%s" %(os.getpid()),"\t", time.ctime())
    start = time.time()
    time.sleep(random.random() * 40)
    end=time.time()
    print ('Task Marlon runs %0.2f seconds.' %(end - start))

def Allen():
    print ("\nRun task Allen-%s" %(os.getpid()),"\t", time.ctime())
    start = time.time()
    time.sleep(random.random() * 30)
    end = time.time()
    print ('Task Allen runs %0.2f seconds.' %(end - start))

def Frank():
    print ("\nRun task Frank-%s" %(os.getpid()),"\t",time.ctime())
    start = time.time()
    time.sleep(random.random() * 20)
    end = time.time()
    print ('Task Frank runs %0.2f seconds.' %(end - start))
        
if __name__=='__main__':
    function_list=  [Lee, Marlon, Allen, Frank] 
    # 获取主进程号
    print ("parent process %s" %(os.getpid()))

    pool=multiprocessing.Pool(4)
    for func in function_list:
        pool.apply_async(func)     #Pool执行函数,apply执行函数,当有一个进程执行完毕后,会添加一个新的进程到pool中

    print ('Waiting for all subprocesses done...')
    pool.close()
    #调用join之前,一定要先调用close() 函数,否则会出错, close()执行后不会有新的进程加入到pool,join函数等待素有子进程结束
    pool.join()    
    print ('All subprocesses done.')
Run task Lee-19936	 Sat May 30 17:51:24 2020
Task Lee, runs 8.44 seconds.

Run task Frank-6316 	 Sat May 30 17:51:24 2020
Task Frank runs 15.17 seconds.

Run task Allen-1996 	 Sat May 30 17:51:24 2020
Task Allen runs 21.64 seconds.

Run task Marlon-8648 	 Sat May 30 17:51:24 2020
Task Marlon runs 31.43 seconds.
All subprocesses done.
例8. 使用pool.map()
import multiprocessing
import os, time, random

def compute(num):
	return str(num**2)+"  "+str(time.ctime())
        
if __name__=='__main__':
    num = [1,2,3,4]
    pool=multiprocessing.Pool(3)
    result = pool.map(compute,num)
    print ('Waiting for all subprocesses done...')
    pool.close()
    #调用join之前,一定要先调用close() 函数,否则会出错, close()执行后不会有新的进程加入到pool,join函数等待素有子进程结束
    pool.join()
    print(type(result), "\t", result)    
    print ('All subprocesses done.'+"\t"+time.ctime())
Waiting for all subprocesses done...
<class 'list'> 	 ['1  Sat May 30 18:03:58 2020', '4  Sat May 30 18:03:58 2020', '9  Sat May 30 18:03:58 2020', '16  Sat May 30 18:03:58 2020']
All subprocesses done.	Sat May 30 18:03:58 2020

2.3 Todo:Pipe&Queue&Event&Semaphore&Lock

Python多进程编程

2.4 注意事项

(1) 在windows下进行多进程编程的时候,主程序一定要加 if name == “main
(2) 获取cpu核心数,即进程池中可以设置的最大进程数,若设置超过该数并不会加快速度

from multiprocessing import cpu_count, Pool
cores = cpu_count()

2.5 cpu 数量

cores = cpu_count()

3. 多线程编程

使用map方法可以获取多线程的返回值

from concurrent.futures import ThreadPoolExecutor
import time

# 参数times用来模拟网络请求的时间
def get_html(times):
    time.sleep(times)
    print("get page {}s finished".format(times))
    return times

executor = ThreadPoolExecutor(max_workers=2)
urls = [3, 2, 4] # 并不是真的url

for data in executor.map(get_html, urls):
    print("in main: get page {}s success".format(data))

输出:

# get page 2s finished
# get page 3s finished
# in main: get page 3s success
# in main: get page 2s success
# get page 4s finished
# in main: get page 4s success

从输出可以看出输出顺序和urls列表的顺序相同,就算2s的任务先执行完成,也会先打印出3s的任务先完成,再打印2s的任务完成
参考ThreadPoolExecutor线程池

4. 总结

在编程中多进程的实现主要有下面两种方法
(1) multiprocessing.Process方法,使用这种方法的好处在于可以使用start()join()方法来控制各个子进程的执行顺序,如现在有三任务,执行顺序如下图所示在这里插入图片描述
即任务2只能在任务1之后执行,任务3可以和其他两个任务同时执行,则可以同过如下方式实现。

import multiprocessing
import time
def worker_1(n):
	time.sleep(n)
	print("task1 finished")

def worker_2(n):
	time.sleep(n)
	print("task2 finished")

def worker_3(n)
	time.sleep(n)
	print("task3 finished")

if __name__ == "__main__":
    p1 = multiprocessing.Process(target = worker_1, args = (1,))
    p2 = multiprocessing.Process(target = worker_2, args = (2,))
    p3 = multiprocessing.Process(target = worker_3, args = (3,))
    p1.start() # 任务1启动
    p3.start() # 任务3启动
    p1.join()  # 等待1结束
    p2.start() # 任务2启动
    # 等待剩下所有任务结束
    p3.join()  
    p2.join()
    # 所有子任务结束,进入主进程
    print("END!!!!!!!!!!!!!!!!!")

(2) 另外一种方法是使用imap方法,该方法最大好处可以顺序返回结果

import multiprocessing
import time
def get_html(n):
    time.sleep(n)
    print("{}s sub_progress success".format(n))
    return n

if __name__ == "__main__": #注意,在windows下这句话必须加!!!
    pool = multiprocessing.Pool(multiprocessing.cpu_count())
    # for result in pool.imap_unordered(get_html, [1, 3, 2]):
    for result in pool.imap(get_html, [1, 3, 2]):
        print("{}s sleep success".format(result))

输出

# 1s sub_progress success
# 1s sleep success
# 2s sub_progress success
# 3s sub_progress success
# 3s sleep success
# 2s sleep success

可以看到,结果是按输入列表的顺序输出。imap方法,结果输出顺序与传入的可迭代对象的顺序相同。imap_unordered方法,结果输出顺序与子进程结束的时间顺序相同。
参考多进程编程

参考

Python多进程编程
Python多进程编程详解
多进程编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值