Day9-Python3基础-多线程、多进程
1、进程、与线程区别
2、python GIL全局解释器锁
3、线程
- 语法
- join
- 线程锁之Lock\Rlock\信号量
- 将线程变为守护进程
- Event事件
- queue队列
- 生产者消费者模型
- Queue队列
- 开发一个线程池
4、进程
- 语法
- 进程间通讯
- 进程池
一、进程与线程
什么是进程(process)?
程序的执行实例称为进程。
每个进程都提供执行程序所需的资源。 进程具有虚拟地址空间,可执行代码,系统对象的打开句柄,
安全上下文,唯一进程标识符,环境变量,优先级类,最小和最大工作集大小以及至少一个执行线程。
每个进程都使用单个线程启动,通常称为主线程,但可以从其任何线程创建其他线程。
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。
程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;
进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。
这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,
因此,进程就是为了在CPU上实现多道编程而提出的。
有了进程为什么还要线程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。
很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,
主要体现在两点上:
-
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
-
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
例如, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,
每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,
为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,
发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?
就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!
什么是线程(thread)?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,
每条线程并行执行不同的任务
线程是执行上下文,它是CPU执行指令流所需的所有信息。
假设你正在读一本书,而你现在想休息一下,但是你希望能够从你停下来的确切位置回来并继续阅读。
实现这一目标的一种方法是记下页码,行号和字号。因此,阅读书籍的执行环境就是这三个数字。
如果你有一个室友,并且她使用相同的技术,她可以在你不使用时拿走这本书,并从她停下的地方继续阅读。
然后你可以把它拿回来,并从你原来的地方恢复。
线程以相同的方式工作。 CPU正在给你一种错觉,即它同时进行多次计算。它通过在每次计算上花费一点时间来做到这一点。
它可以这样做,因为它具有每个计算的执行上下文。就像您可以与朋友共享一本书一样,许多任务可以共享CPU。
在更技术层面上,执行上下文(因此是一个线程)由CPU寄存器的值组成。
最后:线程与进程不同。线程是执行的上下文,而进程是与计算相关联的一堆资源。一个进程可以有一个或多个线程。
澄清:与进程相关联的资源包括内存页面(进程中的所有线程具有相同的内存视图),
文件描述符(例如,打开套接字)和安全凭证(例如,启动该进程的用户的ID)处理)。
进程与线程的区别?
1、线程共享创建它的进程的地址空间; 进程有自己的地址空间。
2、线程可以直接访问其进程的数据段; 进程拥有自己父进程数据段的副本。
3、线程可以直接与其进程的其他线程通信; 进程必须使用进程间通信来与兄弟进程通信。
4、新线程很容易创建; 新流程需要复制父流程。
5、线程可以对同一进程的线程进行相当大的控制; 进程只能控制子进程。
6、对主线程的更改(取消,优先级更改等)可能会影响进程的其他线程的行为; 对父进程的更改不会影响子进程。
Python GIL(Global Interpreter Lock)
在CPython中,全局解释器锁(GIL)是一个互斥锁,它可以防止多个本机线程同时执行Python字节码。
这种锁是必要的,主要是因为CPython的内存管理不是线程安全的。
(但是,由于GIL存在,其他功能已经增长,取决于它强制执行的保证。)
简而言之,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
GIL并不是Python的特性,Python完全可以不依赖于GIL
这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf
Python threading模块
线程有2种调用方式,如下:
直接调用:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading import time def say_hi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=say_hi,args=(1,)) #生成一个线程实例 t2 = threading.Thread(target=say_hi,args=(2,)) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 print(t1.getName()) #获取线程名 print(t2.getName())
继承式调用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定义每个线程要运行的函数 print("running on number:%s" %self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start()
Join & Daemon
有些线程执行后台任务,例如发送keepalive数据包,或执行定期垃圾收集等等。
这些仅在主程序运行时才有用,并且一旦其他非守护程序线程退出就可以将它们终止。
如果没有守护程序线程,您必须跟踪它们,并在程序完全退出之前告诉它们退出。
通过将它们设置为守护程序线程,您可以让它们运行并忘记它们,
当程序退出时,任何守护程序线程都会自动终止。
注意:守护程序线程在关闭时突然停止。 他们的资源(例如打开文件,数据库事务等)可能无法正确发布。
如果您希望线程正常停止,请将它们设置为非守护进程并使用合适的信号机制(如Event)。
join
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading import time def run(n): print("task",n) time.sleep(2) print('task done:',n,threading.current_thread()) obj_res = []#存线程实例 start_time = time.time() for i in range(50): t = threading.Thread(target=run,args=('t-%s'%i,)) obj_res.append(t)#为了不阻塞后面线程的启动,不在这里join t.start() for t in obj_res:#循环线程实例列表,等待所有线程执行完毕 t.join()#wait t的执行结果 stop_time = time.time() #程序本身就是主线程 print("--all threads has finish---",threading.current_thread(),threading.active_count()) print('total time:%s'%(stop_time-start_time))
守护线程
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
''' 守护线程,程序不会等守护线程执行完毕才结束程序。 程序会等主线程执行完就结束程序。 ''' import threading import time def run(n): print("task",n) time.sleep(2) start_time = time.time() for i in range(50): t = threading.Thread(target=run,args=('t-%s'%i,)) t.setDaemon(True)#把当前线程设置为守护线程 t.start() stop_time = time.time() print("--all threads has finish---") print('total time:%s'%(stop_time-start_time))
线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,
也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
Python同一时间只允许一个线程访问,修改数据,很简单,
每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,
可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕
并把锁释放掉后才能再访问此数据。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import time,threading 2 3 lock = threading.Lock()#生成全局锁 4 5 6 7 def addnum(): 8 9 global num#在每个线程中都获取这个全局变量 10 print('---get num:',num) 11 time.sleep(1) 12 lock.acquire()#修改数据前加锁 13 num -= 1#对此公共变量进行-1操作 14 lock.release()#修改后释放 15 16 num =100 #设置一个共享变量 17 18 thread_list = [] 19 20 21 for i in range(100): 22 t = threading.Thread(target=addnum) 23 24 t.start() 25 26 thread_list.append(t) 27 28 for t in thread_list:#等待所有线程执行完毕 29 t.join() 30 31 print('final num:',num)
GIL VS Lock
机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,
为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 。
那你又问了, 既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,
比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,
你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,
此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,
py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,
结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,
即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。
RLock(递归锁)
说白了就是在一个大锁中还要再包含子锁
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading, time def run1(): print("grab the first part data") lock.acquire() global num num += 1 lock.release() return num def run2(): print("grab the second part data") lock.acquire() global num2 num2 += 1 lock.release() return num2 def run3(): lock.acquire() res = run1()#res为run1()函数的返回值,即num的值 print('--------between run1 and run2-----') res2 = run2()#res2为run2()函数的返回值,即num2的值 lock.release() print(res, res2) ''' 递归锁实现原理: 类似于以下方式 { lock1:key1, lock2:key2 } ''' num, num2 = 0, 0 lock = threading.RLock()#设置成递归锁,连续锁好几次必须要用递归锁 for i in range(10): t = threading.Thread(target=run3)#实例化线程 t.start()#启动线程 while threading.active_count() != 1:#检测当前程序运行中的线程数量 print('threading_count:',threading.active_count()) else: print('----all threads done---') print(num, num2)
Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,
比如在自主银行有3个ATM机,有5个人想取钱,那最多只允许3个人进去,后面的人只能等里面有人出来了才能再进去。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#Author:Yun import threading, time ''' 信号量与单个锁的区别是,信号量有多把锁 ''' def run(n): semaphore.acquire()#获取一把锁 time.sleep(1) print("run the thread: %s\n" % n) semaphore.release()#释放锁 if __name__ == '__main__': num = 0 semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 for i in range(20): #循环生成20个线程 t = threading.Thread(target=run, args=(i,)) t.start() while threading.active_count() != 1: pass # print threading.active_count() else: print('----all threads done---') print(num)
Timer
此类表示仅在经过一定时间后才应运行的操作
与线程一样,通过调用start()方法启动计时器。 通过调用thecancel()方法可以停止计时器(在其动作开始之前)。
计时器在执行其操作之前将等待的时间间隔可能与用户指定的时间间隔不完全相同。
timer实例1
#Author:Yun
import time,threading
def hello():
print("\033[44;1m'鲁班大师智障二百五!'\033[0m")
class Timer(threading.Thread):
def __init__(self,sleep_time,func):
self.sleep_time = sleep_time
self.func = func
super(Timer,self).__init__()#继承父类threading.THread的__init__
time.sleep(self.sleep_time)
self.func()
t = Timer(5.0,hello )
t.start() # after 5 seconds, "鲁班大师智障二百五!" will be printed
timer2实例2
BaseTimer模块
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from abc import ABCMeta, abstractmethod import time import threading class BaseTimer(threading.Thread): """ 基础定时器抽象类,可以随意继承本类并且实现exec抽象方法即可定时产生任务 """ __metaclass__ = ABCMeta def __init__(self, howtime=1.0, enduring=True): """ howtime 每次定时的时间 enduring 是否是一个持久性的任务,用这个可以控制开关 """ self.enduring = enduring self.howtime = howtime #threading.Thread.__init__(self) super(BaseTimer,self).__init__()#继承父类的__init__() def run(self): time.sleep(self.howtime) # 至少执行一次 任务 self.exec() while self.enduring: # 是否持久,或者是否进行运行 time.sleep(self.howtime) self.exec() # 每次开始执行任务 @abstractmethod def exec(self): """抽象方法,子类实现""" pass def destroy(self): """销毁自身""" self.enduring = False del self def stop(self): self.enduring = False def restart(self): self.enduring = True def get_status(self): return self.enduring
timer_test.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import BaseTimer from BaseTimer import * class TimerMask(BaseTimer): """定时任务类,不同的业务逻辑""" def __init__(self,howtime=1.0,enduring=True): """ howtime 每次定时的时间 enduring 是否是一个持久性的任务,用这个可以控制开关 """ super(TimerMask,self).__init__(howtime,enduring)#继承父类的__init__() self.ws=0 def exec(self): print("皮卡丘!") self.ws += 1 #这里是过0.8秒输出一次 if self.ws >5: self.destroy()#销毁自身 if __name__ == "__main__": Q_w = 0 w = TimerMask(howtime=0.8,enduring=True) print("-") w.start() #这里线程输出这些,做其他事情的 while True: time.sleep(0.4) #0.4秒 print("- ",Q_w," - WMask:",w) Q_w += 1
Events事件
事件是一个简单的同步对象;
该事件代表一个内部标志和线程
可以等待设置标志,或者自己设置或清除标志。
event = threading.Event()
#客户端线程可以等待设置标志
event.wait()
#a服务器线程可以设置或重置它
event.set()
event.clear()
如果设置了标志,则wait方法不会执行任何操作。
如果该标志被清除,则等待将被阻塞,直到它再次被设置为止。
任意数量的线程都可以等待同一事件。
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,
即启动一个线程做交通指挥灯,生成几个线程做车辆,
车辆行驶按红灯停,绿灯行的规则。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time event = threading.Event() ''' event.set() 标志位设定了,代表绿灯,直行通过 event.clear() 标志位被清空,代表红灯,等待绿灯 ''' def lighter(): count = 0 event.set()#设置标志位,开启绿灯 while True: if count >=5 and count <10:#变红灯 event.clear() print('\033[41;1mred light is on ...\033[0m') elif count >10: event.set()#变会绿灯 count = 0 else: print('\033[42;1mgreen light is on ...\033[0m') count += 1 time.sleep(1) def car(name): while True: if event.is_set():#检测是否设置标志位 print('\033[42;1m[%s] runing ...\033[0m'%name) time.sleep(1) else: print('[%s] light is red,waiting for green' % name) event.wait() print('\033[44;1m[%s] light is green\033[0m'%name) #声明一个light的线程 light = threading.Thread(target=lighter,) #启动light线程 light.start() car1 = threading.Thread(target=car,args=('tesla',)) car1.start()
queue队列
当必须在多个线程之间安全地交换信息时,队列在线程编程中特别有用。
- class
queue.
Queue
(maxsize=0) #先入先出
- class
queue.
LifoQueue
(maxsize=0) #last in fisrt out - class
queue.
PriorityQueue
(maxsize=0) #存储数据时可设置优先级的队列 - 优先级队列的构造函数。 maxsize是一个整数,用于设置可以放入队列的项目数的上限。
- 达到此大小后,插入将阻止,直到消耗队列项。 如果maxsize小于或等于零,则队列大小为无限大。
queue.
Empty
queue.
Full
在已满的Queue对象上调用非阻塞put()(或put_nowait())时引发异常。Queue.
qsize
()
Queue.
empty
() #return True if empty
Queue.
full
() # return True if full
Queue.
put
(item, block=True, timeout=None)- 将项目放入队列。 如果可选的args块为true且timeout为None(默认值),则在必要时阻塞,直到有空闲插槽可用。
- 如果timeout是一个正数,则它会阻止最多超时秒,如果在该时间内没有可用的空闲槽,则会引发Full异常。
- 否则(块为假),如果空闲插槽立即可用,则将项目放在队列中,否则引发完全异常(在这种情况下忽略超时)。
Queue.
put_nowait
(item)- 相当于put(item,False)。
Queue.
get
(block=True, timeout=None)
从队列中删除并返回一个项目。 如果可选的args块为true且timeout为None(默认值),则在必要时阻止,直到某个项可用为止。
如果timeout是一个正数,则它会阻止最多超时秒,如果在该时间内没有可用的项,则会引发Empty异常。
否则(块为假),如果一个项立即可用,则返回一个项,否则引发Empty异常(在这种情况下忽略超时)。
Queue.
get_nowait
()- Equivalent to
get(False)
. - 提供了两种方法来支持跟踪守护进程消费者线程是否已完全处理入队任务
Queue.
task_done
()- 表明以前排队的任务已完成。 由队列使用者线程使用。 对于用于获取任务的每个get(),对task_done()的后续调用会告知队列任务的处理已完成。
如果join()当前正在阻塞,则它将在所有项目都已处理后恢复(这意味着已为每个已放入队列的项目收到task_done()调用)。
如果调用的次数超过队列中放置的项目,则引发ValueError。 Queue.
join
() block直到queue被消费完毕
生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
生产者消费者模型的例子
例1
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading import queue def producer(): for i in range(10): q.put("骨头 %s" % i) print("开始等待所有的骨头被取走...") q.join() print("所有的骨头被取完了...") def consumer(n): while q.qsize() > 0: print("%s 取到" % n, q.get()) q.task_done() # 告知这个任务执行完了 q = queue.Queue() p = threading.Thread(target=producer, ) p.start() c1 = consumer("鲁班")
例2
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time import queue q = queue.Queue(maxsize=10) def Producer(name): count = 0 while True: q.put('骨头%s'%count) print('生产了酱骨头',count) count += 1 time.sleep(0.1) def Consumer(name): #while q.qsize() > 0:#有骨头才吃 while True: print('[%s]取到了[%s],并且吃了它!'%(name,q.get())) time.sleep(1) p = threading.Thread(target=Producer,args=('厨神-程咬金',)) p.start() c = threading.Thread(target=Consumer,args=('鲁班',)) c.start() c1 = threading.Thread(target=Consumer,args=('孟奇',)) c1.start()
例3
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#Author:Yun import time,random import queue,threading q = queue.Queue() def Producer(name): count = 0 while count <20: time.sleep(random.randrange(3)) q.put(count) print('Producer %s has produced %s baozi..' %(name, count)) count +=1 def Consumer(name): count = 0 while count <20: time.sleep(random.randrange(4)) if not q.empty(): data = q.get() print(data) print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data)) else: print("-----no baozi anymore----") count +=1 p1 = threading.Thread(target=Producer, args=('厨神-程咬金',)) c1 = threading.Thread(target=Consumer, args=('吃货-鲁班',)) p1.start() c1.start()
多进程multiprocessing
multiprocessing是一个使用类似于线程模块的API支持产生进程的包。 多处理包提供本地和远程并发,
通过使用子进程而不是线程有效地侧向执行全局解释器锁。 因此,多处理模块允许程序员充分利用给定机器上的多个处理器。
它可以在Unix和Windows上运行。
进程实例一:启动10个进程,每个进程启动一个线程
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import multiprocessing import time import threading def thread_run(): print(threading.get_ident()) def run(name): time.sleep(2) print('hello',name) t = threading.Thread(target=thread_run,) t.start() if __name__ =='__main__': for i in range(10): p = multiprocessing.Process(target=run,args=('鲁班 %s' %i,)) p.start()
进程实例二:显示所涉及的各个进程ID
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#Author:Yun ''' 获取进程ID 每一个进程都是由父进程启动的,每个进程默认有个父进程 ''' import os from multiprocessing import Process def info(title): print(title) print('module name:',__name__) print('parent process:', os.getppid()) print('process id:', os.getpid()) def f(name): info('\033[31;1mcalled from child process function f\033[0m') print('hello',name) if __name__ == '__main__': info('\033[32;1mmain process\033[0m') p = Process(target=f,args=('hehe',)) p.start() p.join()
进程间通讯
不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:
Queues
使用方法跟threading里的queue差不多
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#Author:Yun import threading,queue from multiprocessing import Process,Queue def f(qq): qq.put([42,None,'hehe']) if __name__ == '__main__': #两个进程之间的数据传递 q = Queue() #p = threading.Thread(target=f,) p = Process(target=f,args=(q,)) p.start() print(q.get()) p.join() #两个进程之间的内存是独立的
Pipes
Pipe()函数返回一个由管道连接的连接对象,默认情况下是双工(双向)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#Author:Yun ''' 使用管道的方式实现两个进程的数据传递 ''' from multiprocessing import Process,Pipe def f(conn): conn.send([42,None,'hello from child']) print('from parent:',conn.recv()) if __name__ =='__main__': parent_conn,child_conn = Pipe() p = Process(target=f,args=(child_conn,)) p.start() print('from child:',parent_conn.recv()) parent_conn.send('鲁班大师智障250!') p.join()#等待进程执行结束
Pipe()返回的两个连接对象代表管道的两端。 每个连接对象都有send()和recv()方法(以及其他方法)。
请注意,如果两个进程(或线程)同时尝试读取或写入管道的同一端,则管道中的数据可能会损坏。
当然,同时使用管道的不同端部的过程不存在损坏的风险。
Managers
Manager()返回的管理器对象控制一个服务器进程,该进程保存Python对象并允许其他进程使用代理操作它们。
A manager returned by Manager()
will support
types list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Barrier,Queue,Value and Array。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from multiprocessing import Process,Manager ''' Manager 实现多进程间数据共享,Manager()方法中默认加锁,因为它要把数据同时拷贝多份,保证数据不会乱''' import os def f(d,l): d[os.getpid()] = os.getpid() l.append(os.getpid()) print(l) if __name__ == '__main__': with Manager() as manager: d = manager.dict()#生成一个字典可在多进程间共享、传递 l = manager.list(range(5))#生成一个列表可在多进程间共享、传递 p_list = [] for i in range(10):#启动10个进程 p = Process(target=f,args=(d,l)) p.start() p_list.append(p) for res in p_list:#等待10个进程运行完后的结果 res.join() print(d) print(l)
进程同步
如果不使用来自不同进程的锁定输出,则可能会混淆不清
进程锁
为什么会有进程锁,进程不是独立的吗?
因为多个进程共享一个屏幕,进程锁保证每个进程在屏幕上输出完毕后,再释放屏幕,让其他进程使用。
防止一个进程还没使用完屏幕,屏幕就被另一个进程调走了。
from multiprocessing import Process,Lock def f(L,i): #加锁的目的是防止打印乱序,因为这些多个进程都共享一个显示屏幕 L.acquire()#加锁
try: print('hehe',i) finally:
L.release()#释放锁 if __name__ == '__main__': lock = Lock()#实例化锁 for num in range(100): Process(target=f,args=(lock,num)).start()
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
apply#串行
apply_async#并行
apply实例:
from multiprocessing import Process,Pool
import os,time
def Foo(i):
print('in process',os.getpid())
def Bar(arg):
print('-->exec done:',arg,os.getpid())
if __name__ == '__main__':
pool = Pool(processes=3)#允许进程池同时放入3个进程
print('主进程',os.getpid())
for i in range(10):
pool.apply(func=Foo,args=(i,))#串行
print('end')
pool.close()
pool.join()
#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭
apply_async实例一:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from multiprocessing import Process,Pool import os,time def Foo(i): time.sleep(2) print('in process',os.getpid()) def Bar(arg): print('-->exec done:',arg,os.getpid()) if __name__ == '__main__': pool = Pool(processes=3)#允许进程池同时放入3个进程 print('主进程',os.getpid()) for i in range(10): pool.apply_async(func=Foo,args=(i,))#并行 print('end') pool.close() pool.join()
apply_async实例二:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#Author:Yun from multiprocessing import Process,Pool import os,time def Foo(i): time.sleep(2) print('in process',os.getpid()) def Bar(arg): print('-->exec done:',arg,os.getpid()) if __name__ == '__main__': pool = Pool(processes=3)#允许进程池同时放入3个进程 print('主进程',os.getpid()) for i in range(10): pool.apply_async(func=Foo,args=(i,),callback = Bar) #并行,callback回调函数,当执行完func中的函数,就会执行回调函数 print('end') pool.close() pool.join()