Python多进程与多线程
1、基本概念
2、多线程内容方法
3、多进程内容方法
1、基本概念
1.1 线程
1.1.1 什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,
一个进程中可以并发多个线程,每条线程并行执行不同的任务。A thread is an execution context, which is all the information a CPU needs to
execute a stream of instructions.(一个线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令。)
1.1.2 线程如何工作
Suppose you're reading a book, and you want to take a break right now, but you want to be able to come back and resume reading from the exact point where you stopped.One way to
achieve that is by jotting down the page number, line number, and word number. So your execution context for reading a book is these 3 numbers.
If you have a roommate, and she's using the same technique, she can take the book while you're not using it, and resume reading from where she stopped. Then you can take it back,
and resume it from where you were.
Threads work in the same way. A CPU is giving you the illusion(幻觉) that it's doing multiple computations at the same time. It does that by spending a bit of time on each computation. It can
do that because it has an execution context for each computation. Just like you can share a book with your friend, many tasks can share a CPU.
On a more technical level, an execution context (therefore a thread) consists of the values of the CPU's registers.
1.2 进程
一个程序的执行实例就是一个进程。每一个进程提供执行程序所需的所有资源。(进程本质上是资源的集合)
Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique
process identifier, environment variables, a priority class, minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread,
often called the primary thread, but can create additional threads from any of its threads.
1.3 进程与线程的区别
- Threads share the address space of the process that created it; processes have their own address space.
- Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
- Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
- New threads are easily created; new processes require duplication of the parent process.
- Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
- Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.
- 同一个进程中的线程共享同一内存空间,但是进程之间是独立的。
- 同一个进程中的所有线程的数据是共享的(进程的数据段),进程之间的数据是独立的(进程有自己的父进程数据段的副本)。
- 同一个进程的线程之间可以直接通信,但是进程之间的交流需要借助中间代理来实现。
- 创建新的线程很容易,但是创建新的进程需要对父进程做一次复制。
- 一个线程可以操作同一进程的其他线程,但是进程只能操作其子进程。
- 对主线程的修改可能会影响其他线程的行为,但是父进程的修改(除了删除以外)不会影响其他子进程。
- 线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。
- 线程启动速度快,进程启动速度慢(但是两者运行速度没有可比性)。
2、多线程
2.1 threading模块的初级使用
Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。
Python中使用线程有两种方式:函数或者用类来包装线程对象。
2.1.1 函数式创建
启动一个线程就是把一个函数传入并创建Thread
实例,语法如下:
t = threading.Thread(target=function, args=(kwargs,))
参数说明:
- t - 赋值变量
- function - 线程函数。
- args - 传递给线程函数的参数,他必须是个tuple类型。
- kwargs - 可选参数。
然后调用start()开始执行:
import threading import time def task(n): print('task---%s' % n) time.sleep(2) print('End') t1 = threading.Thread(target=task, args=('t1',)) t2 = threading.Thread(target=task, args=('t2',)) t1.start() t2.start()
执行结果如下:
task---t1 task---t2 End End
2.1.2 继承式创建
threading 模块提供的一些方法:
- threading.current_thread(): 返回当前的线程变量。
- threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.active_count(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
方法 | 注释 |
run() | 线程被cpu调度后自动执行线程对象的run方法,如果想自定义线程类,直接重写run方法就行了 |
start() | 启动线程活动 |
join() | 逐个执行每个线程,执行完毕后继续往下执行 |
isAlive() | 返回线程是否活动的 |
getName() | 返回线程名 |
setName() | 设置线程名 |
setDaemon(True) | 设置为守护线程 |
使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法:
import threading import time class MyThreading(threading.Thread): #继承父类threading.Thread def __init__(self, n): super(MyThreading, self).__init__() self.n = n def run(self): #重构run()方法 print('running task', self.n) time.sleep(1) print('End') if __name__ == '__main__': t1 = MyThreading('t1') t2 = MyThreading('t2') t1.start() #启动线程后,会自动执行run()方法 t2.start() print('The %s has finished' % threading.current_thread) #打印当前线程变量
执行结果如下:
running task t1 running task t2 The <_MainThread(MainThread, started 13204)> has finished End End
补充:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading import time class MyThreading(threading.Thread): def __init__(self, n): super(MyThreading, self).__init__() self.n = n def run(self): print('running task', self.n) time.sleep(1) print('End') if __name__ == '__main__': t1 = MyThreading('t1') t2 = MyThreading('t2') t1.start() t2.start() t1.join() #主线程等待t1的线程完成再往下执行 t2.join() print('The %s has finished' % threading.current_thread()) # 执行结果如下(可对比上一个程序的结果,join()后主线程等待子线程完成再打印): running task t1 running task t2 End End The <_MainThread(MainThread, started 11868)> has finished
2.1.3 几个常用方法
下面通过一个记录线程的例子,进一步了解join()方法和threading.current_thread()、threading.active_count()方法:
import threading import time def task(n): print('task---%s' % n) # 打印子线程 time.sleep(1) print('task %s done,the number of active threads is:%s' % (threading.current_thread(), threading.active_count())) # 打印当前线程变量和活跃线程个数 start_time = time.time() # 记录开始时刻 t_obj = [] # 定义列表存放线程实例 for i in range(5): # 设置5个线程实例 t = threading.Thread(target=task, args=('%s' % i,)) t.start() t_obj.append(t) # 将所有线程存放在列表中 for t in t_obj: # 为所有子线程添加join方法,使主线程等待所有线程完成 t.join() stop_time = time.time() # 记录结束时刻 print('------all threads has finished------', threading.current_thread(), threading.active_count()) # 打印主线程和线程个数 print('const:', stop_time - start_time) # 打印总耗时
执行结果如下:
task---0 task---1 task---2 task---3 task---4 task <Thread(Thread-4, started daemon 11868)> done,the number of active threads is:6 task <Thread(Thread-2, started daemon 7596)> done,the number of active threads is:5 task <Thread(Thread-3, started daemon 8488)> done,the number of active threads is:4 task <Thread(Thread-5, started daemon 9648)> done,the number of active threads is:3 task <Thread(Thread-1, started daemon 15396)> done,the number of active threads is:2 ------all threads has finished------ <_MainThread(MainThread, started 15196)> 1 const: 1.0155258178710938
注意到这里的threading.active_count()方法返回的:线程数量=子线程的数量 + 主线程数量(1)
2.2 守护线程
来看一个简单的守护线程的例子,这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,因此当主线程结束后,子线程也会随之结束。所以当主线程结束后,整个程序就退出了。
import threading import time def task(n): print('task-%s starts' % n) time.sleep(2) print('task-%s end' % n) for i in range(3): t = threading.Thread(target=task, args=('%s' % i,)) t.setDaemon(True) # 应在线程启动前设置守护线程 t.start() print('The main thread end')
执行结果如下:
task-0 starts task-1 starts task-2 starts The main thread end
对比不设置守护进程:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading import time def task(n): print('task-%s starts' % n) time.sleep(2) print('task-%s end' % n) for i in range(3): t = threading.Thread(target=task, args=('%s' % i,)) t.start() print('The main thread end') # 执行结果如下: task-0 starts task-1 starts task-2 starts The main thread end task-0 end task-1 end task-2 end
所以,将子线程设置守护线程后,主线程结束,杀死未执行完的子线程,程序退出;未设置守护线程,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。
关于更多join()和守护线程可移步:python多线程中join()的理解
2.3 GIL
在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少核,同时只能执行一个线程。
究其原因,这就是由于GIL的存在导致的。
GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们
可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython
调用的是c语言的原生线程,所以他不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。
Python多线程的工作过程:
python在使用多线程的时候,调用的是c语言的原生线程。
- 拿到公共数据
- 申请gil
- python解释器调用os原生线程
- os操作cpu执行运算
- 当该线程执行时间到后,无论运算是否已经执行完,gil都被要求释放
- 进而由其他进程重复上面的过程
- 等其他进程执行完后,又会切换到之前的线程(从他记录的上下文继续执行)
整个过程是每个线程执行自己的运算,当执行时间到就进行切换(context switch)。
2.4 线程锁(互斥锁mutex)
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何
一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
举个小例子看下:
import threading # 你开始在坐标原点 position = 0 def move(x): # 定义你的移动 # 向右为正,向左为负 global position position = position + x position = position - x # 回到原点 def run_thread(x): for i in range(500000): move(x) t1 = threading.Thread(target=run_thread, args=(3,)) t2 = threading.Thread(target=run_thread, args=(2,)) t1.start() t2.start() t1.join() t2.join() print('your position:', position)
这里定义了一个position,在move()方法中让它左右移动,理论上无论调用多少次move()方法,position的值最终都是0,但是,由于线程的调度是由操作系统决定的,
当t1、t2交替执行时,只要循环次数足够多,position的结果就不一定是0了。
究竟为什么会出现结果不为0的情况呢?因为cpu在执行move()方法时,需要执行若干条语句已达到修改的目的,而此过程中,线程可能随时中断(线程交替执行),
比如,position = position + 2,执行时会分步执行:
- 计算position + 2,存入临时变量中;
- 将临时变量的值赋给position。
可以写为:
y = position + x
position = y
由于y是局部变量,两个线程各自都有自己的y,t1和t2是交替运行的,如果操作系统以下面的顺序执行t1、t2:
# 初始值 position = 0 t1: y1 = position + 3 # y1 = 0 + 3 = 3 t2: y2 = position + 2 # y2 = 0 + 2 = 2 t2: position = y2 # position = 2 t1: position = y1 # position = 3 t1: y1 = position - 3 # y1 = 3 - 3 = 0 t1: position = y1 # position = 0 t2: y2 = position - 8 # y2 = 0 - 2 = -2 t2: position = y2 # position = -2 # 结果 position = -2
(注:虽然解释器有GIL,同一时间只能有一个线程执行,但数据会被copy成很多份,线程切换过程中,还是会有数据出错)
如果要确保position计算正确,就要给move()上一把锁,当某个线程开始执行move()时,就称该线程因为获得了锁,因此其他线程不能同时执行move(),只能等待,
直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是
通过threading.Lock()来实现:
import threading position = 0 lock = threading.Lock() # 实例化一把锁 def move(x): lock.acquire() # 获取锁 global position position = position + x position = position - x lock.release() # 释放锁 def run_thread(x): for i in range(500000): move(x) t1 = threading.Thread(target=run_thread, args=(3,)) t2 = threading.Thread(target=run_thread, args=(2,)) t1.start() t2.start() t1.join() t2.join() print('your position:', position)
当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处就是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就
大大地下降了。
2.5 递归锁
RLcok类的用法和Lock类一模一样,但它支持嵌套,在多个锁没有释放的时候一般会使用使用RLcok类。
import threading lock = threading.RLock() # 实例化一个递归锁 num = 0 def task(): print('task is working') lock.acquire() global num num += 1 lock.release() return num def foo(): print('foo is working') lock.acquire() res = task() print('acquire num from task:',res) lock.release() for i in range(3): t = threading.Thread(target=foo) t.start()
有锁的嵌套,一定要使用递归锁,如果还使用互斥锁,会出现锁死的情况。
2.6 信号量(Semaphore)
线程(互斥)锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里
面有人出来了才能再进去。
import threading, time def run(n): semaphore.acquire() # 获取信号量 time.sleep(1) print("run the thread: %s\n" % n) semaphore.release() # 释放信号量 if __name__ == '__main__': semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 for i in range(20): t = threading.Thread(target=run, args=(i,)) t.start()
2.7 计时器(Timer)
This class represents an action that should be run only after a certain amount of time has passed
Timers are started, as with threads, by calling their start() method. The timer can be stopped (before its action has begun) by calling the cancel() method.
The interval the timer will wait before executing its action may not be exactly the same as the interval specified by the user.
from threading import Timer def wait(): print('I have wait 2 seconds') t = Timer(2, wait) t.start() # 2秒之后执行wait()
2.8 事件(Event)
python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,对象包含一个可由线程设置的信号标志(flag),它允许线程等待某些事件的发生。
在初始情况下,Event对象中的信号标志(flag)被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志(flag)为假,那么这个线程将会被一直阻塞直至该
标志(flag)为真。一个线程如果将一个Event对象的信号标志(flag)设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对
象,那么它将忽略这个事件, 继续执行。
Event的主要有以下几个方法:
方法 | 注释 |
set | 将flag设置为“True”(设置标志) |
clear | 将flag设置为“False”(清除标志) |
isSet | 判断是否设置了flag("True" or "False") |
wait | 会一直监听flag,如果没有检测到flag(即:flag为"Flase")就一直处于阻塞状态 |
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则:
import threading import time # 定义红绿信号灯 def lighter(): count = 1 event.set() # 初始状态设置flag,wait()不阻塞,即为绿灯 while True: if 5 < count <= 10: event.clear() # 将flag清除,即变红灯 print('---red light on---') elif count > 10: event.set() # 设置flag,即变绿灯 count = 0 else: print('---green light on---') count += 1 time.sleep(1) # 定义行驶车辆 def car(): while True: if event.is_set(): # 检测flag设置,返回"True"即绿灯 print('car is running go go go...') time.sleep(2) else: print('car is waiting light...') event.wait() # flag被清除,等待重新设置,即红灯 print('The green light is on, start going...') if __name__ == '__main__': event = threading.Event() # 创建一个event实例 t = threading.Thread(target=lighter,) c = threading.Thread(target=car,) t.start() c.start()
2.9 队列(queue)
Queue的种类:
- FIFO:
Queue.Queue(maxsize=0)
FIFO即First in First Out,先进先出。Queue提供了一个基本的FIFO容器,使用方法很简单,maxsize是个整数,指明了队列中能存放的数据个数的上限。一旦达到上
限,插入会导致阻塞,直到队列中的数据被消费掉。如果maxsize小于或者等于0,队列大小没有限制。
- LIFO
Queue.LifoQueue(maxsize=0)
LIFO即Last in First Out,后进先出。与栈的类似,使用也很简单,maxsize用法同上
- priority
class Queue.PriorityQueue(maxsize=0)
构造一个优先队列。maxsize用法同上。
常用方法:
方法 | 注释 |
Queue.qsize() | 返回队列的大小 |
Queue.empty() | 如果队列为空,返回True,反之False |
Queue.full() | 如果队列满了,返回True,反之False |
Queue.put(item,block=True, timeout=None) | 写入队列,timeout等待时间 |
Queue.get(block=True, timeout=None) | 获取队列,timeout:等待时间 |
Queue.put_nowait(item) | 相当Queue.put(item, False) |
Queue.get_nowait() | 相当Queue.get(False) |
Queue.task_done() | 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号 |
Queue.join() | 实际上意味着等到队列为空,再执行别的操 |
2.9.1生产者消费者模型:
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么
生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问
题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者
生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了
生产者和消费者的处理能力。
来看一个模型例子:
import threading,queue,time count = 0 # 定义生产者 def producer(name): global count while q.qsize() < 20: # 队列的<20时 count += 1 q.put(count) # 将数据放入队列 print('[%s] has produced %s bones...' % (name, count)) time.sleep(0.5) else: #队列达到20时,暂停生产25秒 time.sleep(25) return producer(name) # 定义消费者 def consumer(name): while True: data = q.get() # 获取队列赋值给data print('[%s] has eaten the number of %s bone...' % (name, data)) time.sleep(3) if __name__ == '__main__': q = queue.Queue() # 实例化一个队列 t = threading.Thread(target=producer, args=('Eric',)) c1 = threading.Thread(target=consumer, args=('yuanyuan',)) c2 = threading.Thread(target=consumer, args=('fangfang',)) t.start() c1.start() c2.start()
3、多进程
multiprocessing模块就是跨平台版本的多进程模块(Unix/Linux操作系统提供了一个fork()系统调用),multiprocessing模块提供了一个Process类来代表一个进程对象,
用法和threading类似:
from multiprocessing import Process import os, time def run_pro(name): print('Run child Process: %s[%s]' % (name, os.getpid())) # 获取进程ID if __name__ == '__main__': p = Process(target=run_pro, args=('num1',)) print('Process will start...') time.sleep(1) print('Main Process is:',os.getpid()) p.start() p.join()
3.1 进程间的通信
Process之间肯定是需要通信的,Python的multiprocess模块提供了Queue、Pipes等多种方式来交换数据。
3.1.1 Queue
这里以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据(使用方法跟threading里的queue差不多)
#!/usr/bin/python # -*- coding: UTF-8 -*- from multiprocessing import Queue,Process import os # 定义写入方法 def write(n): print('Process to write %s' % os.getpid()) for i in ['X', 'Y', 'Z']: n.put(i) # 将数据写入队列 print('Put %s to queue' % i) # 定义读取方法 def read(n): print('Process to read %s' % os.getpid()) while True: print('Get %s to queue' % n.get()) # 从队列读取数据 if __name__ == '__main__': q = Queue() p1 = Process(target=write, args=(q,)) p2 = Process(target=read, args=(q,)) p1.start() p2.start() p1.join() p2.terminate() #读取是死循环,强制结束
执行结果如下:
Process to write 8492 Process to read 9860 Put X to queue Get X to queue Put Y to queue Get Y to queue Put Z to queue Get Z to queue
3.1.2 pipe
Pipe的本质是进程之间的数据传递,而不是数据共享,这和socket有点像。pipe()返回两个连接对象分别表示管道的两端,每端都有send()
和recv()方法。如果两个进程试图在同一时间的同一端进行读取和写入那么,这可能会损坏管道中的数据
from multiprocessing import Process, Pipe import time # 发送数据 def parent(conn): for i in range(5): conn.send(i) print('\033[41;1m send [%s] from parent\033[0m' % i) time.sleep(0.1) # 为了更清晰看出发送和接收的状态 # 接收数据 def child(conn): while True: print('\033[46;1m child has received [%s] \033[0m' % conn.recv()) if __name__ == '__main__': child_pipe, parent_pipe = Pipe() # 实例化一个管道(一个管道有两端) p = Process(target=parent, args=(parent_pipe,)) c = Process(target=child, args=(child_pipe,)) p.start() c.start() p.join() c.terminate() # 强制结束
执行结果如下:
send [0] from parent child has received [0] send [1] from parent child has received [1] send [2] from parent child has received [2] send [3] from parent child has received [3] send [4] from parent child has received [4]
3.2 Manager
(Queue and Pipe objects should only be shared between processes through inheritance.)
通过Manager可实现进程间数据的共享。Manager()返回的manager对象会通过一个服务进程,来使其他进程通过代理的方式操作python对象。
manager对象支持list
, dict
, Namespace
, Lock
, RLock
, Semaphore
, BoundedSemaphore
, Condition
, Event
, Barrier
, Queue
, Value
and Array.
from multiprocessing import Manager,Process import os def fanc(d): d[os.getpid()]= os.getpid() # 在字典中添加键值均为进程ID print(d) if __name__ == '__main__': with Manager() as manager: # 等同于manager = Manager() d = manager.dict() # 创建一个字典,可以在多个进程之间共享和传递 p_list = [] # 创建一个保存进程的列表,方便后面join for i in range(10): p = Process(target=fanc, args=(d,)) p.start() p_list.append(p) for res in p_list: res.join()
通过上面的设置,代码中的10个进程均可以对字典d做修改,达到数据共享的目的,执行结果如下:
{13704: 13704} {13704: 13704, 15824: 15824} {13704: 13704, 15824: 15824, 4460: 4460} {13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292} {13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252} {13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716} {13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716, 1752: 1752} {13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716, 1752: 1752, 11956: 11956} {13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716, 1752: 1752, 11956: 11956, 15048: 15048} {13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716, 1752: 1752, 11956: 11956, 15048: 15048, 7028: 7028}
3.3 进程锁(进程同步)
数据输出的时候保证不同进程的输出内容在同一块屏幕正常显示,防止数据乱序的情况。
Without using the lock output from the different processes is liable to get all mixed up.
3.4 进程池
由于进程启动的开销比较大,使用多进程的时候会导致大量内存空间被消耗。为了防止这种情况发生可以使用进程池,(由于启动线程的
开销比较小,所以不需要线程池这种概念,多线程只会频繁得切换cpu导致系统变慢,并不会占用过多的内存空间)
进程池主要有两个方法:
apply:同步执行(串行)
apply_acync:异步执行(并行)
from multiprocessing import Process,Pool import time,os def foo(i): time.sleep(1) print('in the process:' % os.getpid()) return i+100 def bar(arg): time.sleep(0.1) print('-->exec done:', arg, os.getpid()) if __name__ == '__main__': pool = Pool(5) # 定义一个容量为5的进程池 for i in range(10): # 写入十个进程,多出的会自动挂起 # pool.apply(func=foo, args=(i,)) # 串行方式 pool.apply_async(func=foo, args=(i,), callback=bar) # 并行方式,func子进程执行完后,才会执行callback(回调函数),否则callback不执行(而且callback是由父进程来执行了) pool.close() # 注意这里结束时一定要先close,后join。 pool.join() print('end')
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close()调用close()之后就不能继续添加新的Procsee了。
进程池内部维护一个进程序列,当使用时,去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到
进程池中有可用进程为止。在上面的程序中产生了10个进程,但是只能有5同时被放入进程池,剩下的都被暂时挂起,并不占用内存空间,
等前面的五个进程执行完后,再执行剩下5个进程。