进程与线程
1.进程
我们电脑的应用程序,都是进程,假设我们用的电脑是单核的,cpu同时只能执行一个进程。当程序处于I/O阻塞的时候,CPU如果和程序一起等待,那就太浪费了,cpu会去执行其他的程序,此时就涉及到切换,切换前要保存上一个程序运行的状态,才能恢复,所以就需要有个东西来记录这个东西,就可以引出进程的概念了。
进程就是一个程序在一个数据集上的一次动态执行过程。进程由程序,数据集,进程控制块三部分组成。程序用来描述进程哪些功能以及如何完成;数据集是程序执行过程中所使用的资源;进程控制块用来保存程序运行的状态
2.线程
一个进程中可以开多个线程,为什么要有进程,而不做成线程呢?因为一个程序中,线程共享一套数据,如果都做成进程,每个进程独占一块内存,那这套数据就要复制好几份给每个程序,不合理,所以有了线程。
线程又叫轻量级进程,是一个基本的cpu执行单元,也是程序执行过程中的最小单元。一个进程最少也会有一个主线程,在主线程中通过threading模块,在开子线程
3.进程线程的关系
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程
(2)资源分配给进程,进程是程序的主体,同一进程的所有线程共享该进程的所有资源
(3)cpu分配给线程,即真正在cpu上运行的是线程
(4)线程是最小的执行单元,进程是最小的资源管理单元
4.并行和并发
并行处理是指计算机系统中能同时执行两个或多个任务的计算方法,并行处理可同时工作于同一程序的不同方面
并发处理是同一时间段内有几个程序都在一个cpu中处于运行状态,但任一时刻只有一个程序在cpu上运行。
并发的重点在于有处理多个任务的能力,不一定要同时;而并行的重点在于就是有同时处理多个任务的能力。并行是并发的子集
以上所说的是相对于所有语言来说的,Python的特殊之处在于Python有一把GIL锁,这把锁限制了同一时间内一个进程只能有一个线程能使用cpu
1. 进程 VS 程序
编写完毕的代码,在没有运⾏的时候,称之为程序正在运⾏着的代码,就成为进程进程,除了包含代码以外,还有需要运⾏的环境等,所以和程序是有区别的
进程VS线程
功能
进程,能够完成多任务,⽐如 在⼀台电脑上能够同时运⾏多个QQ 线程,能够完成多任务,⽐如 ⼀个QQ中的多个聊天窗⼝
定义的不同
进程是系统进⾏资源分配和调度的⼀个独⽴单位.
线程是进程的⼀个实体,是CPU调度和分派的基本单位,它是⽐进程更⼩的 能独⽴运⾏的基本单位.线程⾃⼰基本上不拥有系统资源,只拥有⼀点在运 ⾏中必不可少的资源(如程序计数器,⼀组寄存器和栈),但是它可与同属⼀ 个进程的其他的线程共享进程所拥有的全部资源.
区别
⼀个程序⾄少有⼀个进程,⼀个进程⾄少有⼀个线程.
线程的划分尺度⼩于进程(资源⽐进程少),使得多线程程序的并发性⾼。
进程在执⾏过程中拥有独⽴的内存单元,⽽多个线程共享内存,从⽽极 ⼤地提⾼了程序的运⾏效率
线线程不能够独⽴执⾏,必须依存在进程中
优缺点
线程和进程在使⽤上各有优缺点:线程执⾏开销⼩,但不利于资源的管理和 保护;⽽进程正相反。
进程VS线程功能
进程,能够完成多任务,⽐如 在⼀台电脑上能够同时运⾏多个QQ 线程,能够完成多任务,⽐如 ⼀个QQ中的多个聊天窗⼝
定义的不同
进程是系统进⾏资源分配和调度的⼀个独⽴单位.
线程是进程的⼀个实体,是CPU调度和分派的基本单位,它是⽐进程更⼩的能独⽴运⾏的基本单位.线程⾃⼰基本上不拥有系统资源,只拥有⼀点在运⾏中必不可少的资源(如程序计数器,⼀组寄存器和栈),但是它可与同属⼀个进程的其他的线程共享进程所拥有的全部资源.
区别
⼀个程序⾄少有⼀个进程,⼀个进程⾄少有⼀个线程.
线程的划分尺度⼩于进程(资源⽐进程少),使得多线程程序的并发性⾼。
进程在执⾏过程中拥有独⽴的内存单元,⽽多个线程共享内存,从⽽极
⼤地提⾼了程序的运⾏效率
线线程不能够独⽴执⾏,必须依存在进程中优缺点
线程和进程在使⽤上各有优缺点:线程执⾏开销⼩,但不利于资源的管理和保护;⽽进程正相反。
多线程-threading
python的thread模块是⽐较底层的模块,python的threading 模块是对thread做了⼀些包装的,可以更加⽅便的被使⽤
1. 使⽤threading模块
单线程执⾏
#coding=utf-8 def saySorry(): print("亲爱的,我错了,我能吃饭了吗?") time.sleep(1) if __name__ == "__main__": for i in range(5): saySorry() |
运⾏结果:
多线程执⾏
#coding=utf-8
import threading
import time
def saySorry():
print("亲爱的,我错了,我能吃饭了吗?")
time.sleep(1)
if __name__ == "__main__": for i in range(5):
t = threading.Thread(target=saySorry)
t.start() #启动线程,即让线程开始执⾏
运⾏结果:
说明
- 可以明显看出使⽤了多线程并发的操作,花费时间要短很多
- 创建好的线程,需要调⽤ start() ⽅法来启动
2. 主线程会等待所有的⼦线程结束后才结束
#coding=utf-8 import threading from time import sleep,ctime def sing(): print("正在唱歌...%d"%i) sleep(1) def dance(): print("正在跳舞...%d"%i) sleep(1) |
if __name__ == '__main__': print('---开始---:%s'%ctime()) t1 = threading.Thread(target=sing) t1.start() t2.start() #sleep(5) # 屏蔽此⾏代码,试试看,程序是否会⽴⻢结束? print('---结束---:%s'%ctime()) |
3. 查看线程数量
#coding=utf-8 import threading from time import sleep,ctime def sing(): print("正在唱歌...%d"%i) sleep(1) def dance(): print("正在跳舞...%d"%i) sleep(1) |
if __name__ == '__main__': print('---开始---:%s'%ctime()) t1 = threading.Thread(target=sing) t1.start() t2.start() while True: length = len(threading.enumerate()) sleep(0.5) |
threading注意点
1. 线程执⾏代码的封装
通过上⼀⼩节,能够看出,通过使⽤threading模块能完成多任务的程序开
发,为了让每个线程的封装性更完美,所以使⽤threading模块时,往往会定义⼀个新的⼦类class,只要继承 threading.Thread 就可以了,然后重写 run ⽅法示例如下:
#coding=utf-8 class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是 print(msg) if __name__ == '__main__': t = MyThread() t.start() |
说明
python的threading.Thread类有⼀个run⽅法,⽤于定义线程的功能函
数,可以在⾃⼰的线程类中覆盖该⽅法。⽽创建⾃⼰的线程实例后,通
过Thread类的start⽅法,可以启动该线程,交给python虚拟机进⾏调度,当该线程获得执⾏的机会时,就会调⽤run⽅法执⾏线程。
2. 线程的执⾏顺序
#coding=utf-8 class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = "I'm "+self.name+' @ '+str(i) print(msg) def test(): t.start() if __name__ == '__main__': test() |
执⾏结果:(运⾏的结果可能不⼀样,但是⼤体是⼀致的)
I'm Thread-1 @ 0
I'm Thread-2 @ 0
I'm Thread-5 @ 0
I'm Thread-3 @ 0
I'm Thread-4 @ 0
I'm Thread-3 @ 1
I'm Thread-4 @ 1
I'm Thread-5 @ 1 I'm Thread-1 @ 1
I'm Thread-2 @ 1 I'm Thread-4 @ 2 I'm Thread-5 @ 2
I'm Thread-2 @ 2 I'm Thread-1 @ 2 I'm Thread-3 @ 2
说明
从代码和执⾏结果我们可以看出,多线程程序的执⾏顺序是不确定的。当执⾏到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进⼊就绪(Runnable)状态,等待调度。⽽线程调度将⾃⾏选择⼀个线程执⾏。上⾯的代码中只能保证每个线程都运⾏完整个run函数,但是线程的启动顺序、
run函数中每次循环的执⾏顺序都不能确定。
3. 总结
- 每个线程⼀定会有⼀个名字,尽管上⾯的例⼦中没有指定线程对象的
name,但是python会⾃动为线程指定⼀个名字。 - 当线程的run()⽅法结束时该线程完成。
- ⽆法控制线程调度程序,但可以通过别的⽅式来影响线程调度的⽅式。
- 线程的⼏种状态
多线程-共享全局变量
from threading import Thread g_num = 100 def work1(): global g_num print("----in work1, g_num is %d---"%g_num) def work2(): global g_num print("----in work2, g_num is %d---"%g_num) print("---线程创建之前g_num is %d---"%g_num) t1 = Thread(target=work1) #延时⼀会,保证t1线程中的事情做完 time.sleep(1) t2 = Thread(target=work2) |
运⾏结果:
---线程创建之前g_num is 100------in work1, g_num is 103---
----in work2, g_num is 103---
列表当做实参传递到线程中
from threading import Thread def work1(nums): print("----in work1---",nums) def work2(nums): #延时⼀会,保证t1线程中的事情做完 time.sleep(1) print("----in work2---",nums) g_nums = [11,22,33] t1 = Thread(target=work1, args=(g_nums,)) t1.start() t2 = Thread(target=work2, args=(g_nums,)) t2.start() |
运⾏结果:
----in work1--- [11, 22, 33, 44]
----in work2--- [11, 22, 33, 44]
总结:
在⼀个进程内的所有线程共享全局变量,能够在不适⽤其他⽅式的前提下完成多线程之间的数据共享(这点要⽐多进程要好)
缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程⾮安全)
同步的概念
1. 多线程开发可能遇到的问题
假设两个线程t1和t2都要对num=0进⾏增1运算,t1和t2都各对num修改10
次,num的最终的结果应该为20。
但是由于是多线程访问,有可能出现下⾯情况:
在num=0时,t1取得num=0。此时系统把t1调度为”sleeping”状态,把t2转换为”running”状态,t2也获得num=0。然后t2对得到的值进⾏加1并赋给num,使得num=1。然后系统⼜把t2调度为”sleeping”,把t1转为”running”。线程t1
⼜把它之前得到的0加1后赋值给num。这样,明明t1和t2都完成了1次加1⼯作,但结果仍然是num=1。
from threading import Thread g_num = 0 def test1(): global g_num print("---test1---g_num=%d"%g_num) def test2(): global g_num print("---test2---g_num=%d"%g_num) |
p1 = Thread(target=test1) # time.sleep(3) #取消屏蔽之后 再次运⾏程序,结果会不⼀样,,,为啥呢? p2 = Thread(target=test2) print("---g_num=%d---"%g_num) |
运⾏结果(可能不⼀样,但是结果往往不是2000000):
---g_num=284672---
---test1---g_num=1166544
---test2---g_num=1406832
取消屏蔽之后,再次运⾏结果如下:
---test1---g_num=1000000
---g_num=1041802---
---test2---g_num=2000000
问题产⽣的原因就是没有控制多个线程对同⼀资源的访问,对数据造成破坏,使得线程运⾏的结果不可预期。这种现象称为“线程不安全”。
2. 什么是同步
同步就是协同步调,按预定的先后次序进⾏运⾏。如:你说完,我再说。
"同"字从字⾯上容易理解为⼀起动作其实不是,"同"字应是指协同、协助、互相配合。如进程、线程同步,可理解为进程或线程A和B⼀块配合,A执⾏到⼀定程度时要依靠B的某个结果,于是停下来,示意B运⾏;B依⾔执⾏,再将结果给
A;A再继续操作。
3. 解决问题的思路
对于本⼩节提出的那个计算错误的问题,可以通过 线程同步 来进⾏解决思路,如下:
- 系统调⽤t1,然后获取到num的值为0,此时上⼀把锁,即不允许其他现在操作num
- 对num的值进⾏+1
- 解锁,此时num的值为1,其他的线程就可以使⽤num了,⽽且是num的值不是0⽽是1
- 同理其他线程在对num进⾏修改时,都要先上锁,处理完后再解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性
互斥锁
当多个线程⼏乎同时修改某⼀个共享数据的时候,需要进⾏同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引⼊互斥锁。
互斥锁为资源引⼊⼀个状态:锁定/⾮锁定。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“⾮锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性。
threading模块中定义了Lock类,可以⽅便的处理锁定:
#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([blocking])
#释放
mutex.release()
其中,锁定⽅法acquire可以有⼀个blocking参数。
如果设定blocking为True,则当前线程会堵塞,直到获取到这个锁为⽌
(如果没有指定,那么默认为True)
如果设定blocking为False,则当前线程不会堵塞使⽤互斥锁实现上⾯的例⼦的代码如下:
from threading import Thread, Lock import time g_num = 0 def test1(): global g_num #True表示堵塞 即如果这个锁在上锁之前已经被上锁了,那么这个线程会在 #False表示⾮堵塞,即不管本次调⽤能够成功上锁,都不会卡在这, mutexFlag = mutex.acquire(True) if mutexFlag: g_num += 1 mutex.release() print("---test1---g_num=%d"%g_num) def test2(): global g_num mutexFlag = mutex.acquire(True) #True表示堵塞 if mutexFlag: g_num += 1 mutex.release() |
⽽是继续
print("---test2---g_num=%d"%g_num) #创建⼀个互斥锁 #这个所默认是未上锁的状态 mutex = Lock() p1 = Thread(target=test1) p2 = Thread(target=test2) print("---g_num=%d---"%g_num) |
运⾏结果:
---g_num=61866---
---test1---g_num=1861180
---test2---g_num=2000000
可以看到,加⼊互斥锁后,运⾏结果与预期相符。
上锁解锁过程
当⼀个线程调⽤锁的acquire()⽅法获得锁时,锁就进⼊“locked”状态。
每次只有⼀个线程可以获得锁。如果此时另⼀个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调⽤锁的
release()⽅法释放锁之后,锁进⼊“unlocked”状态。
线程调度程序从处于同步阻塞状态的线程中选择⼀个来获得锁,并使得该线程进⼊运⾏(running)状态。
总结
锁的好处:
确保了某段关键代码只能由⼀个线程从头到尾完整地执⾏锁的坏处:
阻⽌了多线程并发执⾏,包含锁的某段代码实际上只能以单线程模式执
⾏,效率就⼤⼤地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对⽅持有的锁时,可能会造成死锁
多线程-⾮共享数据
对于全局变量,在多线程中要格外⼩⼼,否则容易造成数据错乱的情况发⽣
1. ⾮全局变量是否要加锁呢?
#coding=utf-8 import threading class MyThread(threading.Thread): # 重写 构造⽅法 def __init__(self,num,sleepTime): threading.Thread.__init__(self) self.num = num self.sleepTime = sleepTime def run(self): self.num += 1 time.sleep(self.sleepTime) print('线程(%s),num=%d'%(self.name, self.num)) if __name__ == '__main__': mutex = threading.Lock() t1 = MyThread(100,5) t1.start() t2 = MyThread(200,1) t2.start() |
运⾏结果:
多线程-⾮共享数据
import threading def test(sleepTime): num=1 sleep(sleepTime) num+=1 print('---(%s)--num=%d'%(threading.current_thread(), num t1 = threading.Thread(target = test,args=(5,)) t2 = threading.Thread(target = test,args=(1,)) t1.start() |
⼩总结
在多线程开发中,全局变量是多个线程都共享的数据,⽽局部变量等是各⾃线程的,是⾮共享的
多线程-⾮共享数据
死锁现实社会中,男⼥双⽅都在等待对⽅先道歉
如果双⽅都这样固执的等待对⽅先开⼝,弄不好,就分搜了
1. 死锁
在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时等待对⽅的资源,就会造成死锁。
尽管死锁很少发⽣,但⼀旦发⽣就会造成应⽤的停⽌响应。下⾯看⼀个死锁的例⼦
#coding=utf-8 class MyThread1(threading.Thread): def run(self): |
if mutexA.acquire(): print(self.name+'----do1---up----') time.sleep(1) if mutexB.acquire(): print(self.name+'----do1---down----') mutexB.release() class MyThread2(threading.Thread): def run(self): if mutexB.acquire(): print(self.name+'----do2---up----') time.sleep(1) if mutexA.acquire(): print(self.name+'----do2---down----') mutexA.release() mutexB.release() mutexA = threading.Lock() if __name__ == '__main__': t1 = MyThread1() |
运⾏结果:
此时已经进⼊到了死锁状态,可以使⽤ctrl-z退出
2. 说明
3. 避免死锁
程序设计时要尽量避免(银⾏家算法) 添加超时时间等
附录-银⾏家算法
[背景知识]
⼀个银⾏家如何将⼀定数⽬的资⾦安全地借给若⼲个客户,使这些客户既能借到钱完成要⼲的事,同时银⾏家⼜能收回全部资⾦⽽不⾄于破产,这就是银⾏家问题。这个问题同操作系统中资源分配问题⼗分相似:银⾏家就像⼀个操作系统,客户就像运⾏的进程,银⾏家的资⾦就是系统的资源。
[问题的描述] ⼀个银⾏家拥有⼀定数量的资⾦,有若⼲个客户要贷款。每个客户须在⼀开始就声明他所需贷款的总额。若该客户贷款总额不超过银⾏家的资⾦总数,银⾏家可以接收客户的要求。客户贷款是以每次⼀个资⾦单位(如1万RMB
等)的⽅式进⾏的,客户在借满所需的全部单位款额之前可能会等待,但银
⾏家须保证这种等待是有限的,可完成的。
例如:有三个客户C1,C2,C3,向银⾏家借款,该银⾏家的资⾦总额为10
个资⾦单位,其中C1客户要借9各资⾦单位,C2客户要借3个资⾦单位,C3
客户要借8个资⾦单位,总计20个资⾦单位。某⼀时刻的状态如图所示。
对于a图的状态,按照安全序列的要求,我们选的第⼀个客户应满⾜该客户所需的贷款⼩于等于银⾏家当前所剩余的钱款,可以看出只有C2客户能被满
⾜:C2客户需1个资⾦单位,⼩银⾏家⼿中的2个资⾦单位,于是银⾏家把1
个资⾦单位借给C2客户,使之完成⼯作并归还所借的3个资⾦单位的钱,进
⼊b图。同理,银⾏家把4个资⾦单位借给C3客户,使其完成⼯作,在c图
中,只剩⼀个客户C1,它需7个资⾦单位,这时银⾏家有8个资⾦单位,所以
C1也能顺利借到钱并完成⼯作。最后(⻅图d)银⾏家收回全部10个资⾦单位,保证不赔本。那麽客户序列{C1,C2,C3}就是个安全序列,按照这个序列贷款,银⾏家才是安全的。否则的话,若在图b状态时,银⾏家把⼿中的4
个资⾦单位借给了C1,则出现不安全状态:这时C1,C3均不能完成⼯作,
⽽银⾏家⼿中⼜没有钱了,系统陷⼊僵持局⾯,银⾏家也不能收回投资。
综上所述,银⾏家算法是从当前状态出发,逐个按安全序列检查各客户谁能完成其⼯作,然后假定其完成⼯作且归还全部贷款,再进⽽检查下⼀个能完成⼯作的客户,......。如果所有客户都能完成⼯作,则找到⼀个安全序列,银⾏家才是安全的。
同步应⽤多个线程有序执⾏
from threading import Thread,Lock class Task1(Thread): def run(self): while True: if lock1.acquire(): print("------Task 1 -----") sleep(0.5) lock2.release() class Task2(Thread): def run(self): while True: if lock2.acquire(): print("------Task 2 -----") sleep(0.5) lock3.release() class Task3(Thread): def run(self): while True: if lock3.acquire(): print("------Task 3 -----") sleep(0.5) lock1.release() #使⽤Lock创建出的锁默认没有“锁上” lock1 = Lock() #创建另外⼀把锁,并且“锁上” |
lock2 = Lock() #创建另外⼀把锁,并且“锁上” lock3 = Lock() t1 = Task1() t1.start() |
运⾏结果:
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 ----...省略...
总结
可以使⽤互斥锁完成多个任务,有序的进程⼯作,这就是线程的同步
⽣产者与消费者模式
- 1. 队列先进先出
- 2. 栈先进后出
Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先⼊先出)队列Queue,LIFO(后⼊先出)队列LifoQueue,和优先级队列
PriorityQueue。这些队列都实现了锁原语(可以理解为原⼦操作,即要么不做,要么就做完),能够在多线程中直接使⽤。可以使⽤队列来实现线程间的同步。
⽤FIFO队列实现上述⽣产者与消费者问题的代码如下:
#encoding=utf-8 #python2中 from Queue import Queue #python3中 # from queue import Queue class Producer(threading.Thread): def run(self): |
global queue count = 0 while True: if queue.qsize() < 1000: for i in range(100): count = count +1 class Consumer(threading.Thread): def run(self): global queue msg = self.name + '消费了 '+queue.get() if __name__ == '__main__': queue = Queue() for i in range(500): queue.put('初始产品'+str(i)) for i in range(2): p = Producer() p.start() for i in range(5): c = Consumer() c.start() |
3. Queue的说明
- 对于Queue,在多线程通信之间扮演重要的⻆⾊
- 添加数据到队列中,使⽤put()⽅法
- 从队列中取数据,使⽤get()⽅法
- 判断队列中是否还有数据,使⽤qsize()⽅法
4. ⽣产者消费者模式的说明
为什么要使⽤⽣产者和消费者模式
在线程世界⾥,⽣产者就是⽣产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果⽣产者处理速度很快,⽽消费者处理速度很慢,那么⽣产者就必须等待消费者处理完,才能继续⽣产数据。同样的道理,如果消费者的处理能⼒⼤于⽣产者,那么消费者就必须等待⽣产者。为了解决这个问题于是引⼊了⽣产者和消费者模式。
什么是⽣产者消费者模式
⽣产者消费者模式是通过⼀个容器来解决⽣产者和消费者的强耦合问题。⽣产者和消费者彼此之间不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产者⽣产完数据之后不⽤等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取,阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒。
这个阻塞队列就是⽤来给⽣产者和消费者解耦的。纵观⼤多数设计模式,都会找⼀个第三者出来进⾏解耦,
四、多进程
python 中有一把全局锁(GIL)使得多线程无法使用多核,但是如果是多进程,这把锁就限制不了了。如何开多个进程呢,需要导入一个multiprocessing模块
import multiprocessing import time def foo():
print('ok')
time.sleep(2) if __name__ == '__main__':#必须是这个格式
p=multiprocessing.Process(target=foo,args=())
p.start()
print('ending')
虽然可以开多进程,但是一定注意进程不能开太多,因为进程间切换非常消耗系统资源,如果开上千个子进程,系统会崩溃的,而且进程间的通信也是个问题。所以,进程能不用就不用,能少用就少用
1. 进程间的通信
进程间通信有两种方式,队列和管道
1.1 进程间的队列
每个进程在内存中都是独立的一块空间,不像线程那样可以共享数据,所以只能由父进程通过传参的方式把队列传给子进程
import multiprocessing
import threading def foo(q):
q.put([12,'hello',True]) if __name__ =='__main__':
q=multiprocessing.Queue()#创建进程队列 #创建一个子线程
p=multiprocessing.Process(target=foo,args=(q,))
#通过传参的方式把这个队列对象传给父进程
p.start() print(q.get())
1.2 管道
socket其实就是管道,客户端 的sock和服务端的conn是管道 的两端,在进程中也是这个玩法,也要有管道的两头
from multiprocessing import Pipe,Process def foo(sk):
sk.send('hello')#主进程发消息
print(sk.recv())#主进程收消息 sock,conn=Pipe()#创建了管道的两头
if __name__ == '__main__': p=Process(target=foo,args=(sock,))
p.start() print(conn.recv())#子进程接收消息
conn.send('hi son')#子进程发消息
2. 进程间的数据共享
我们已经通过进程队列和管道两种方式实现了进程间的通信,但是还没有实现数据共享
进程间的数据共享需要引用一个manager对象实现,使用的所有的数据类型都要通过manager点的方式去创建
from multiprocessing import Process
from multiprocessing import Manager
def foo(l,i):
l.append(i*i) if __name__ == '__main__':
manager = Manager() Mlist = manager.list([11,22,33])#创建一个共享的列表 l=[]
for i in range(5):
#开辟5个子进程
p = Process(target=foo, args=(Mlist,i))
p.start()
l.append(p)
for i in l:
i.join()#join 方法是等待进程结束后再执行下一个
print(Mlist)
3. 进程池
进程池的作用是维护一个最大的进程量,如果超出设置的最大值,程序就会阻塞,知道有可用的进程为止
from multiprocessing import Pool import time def foo(n):
print(n)
time.sleep(2) if __name__ == '__main__':
pool_obj=Pool(5)#创建进程池 #通过进程池创建进程
for i in range(5):
p=pool_obj.apply_async(func=foo,args=(i,))
#p是创建的池对象
# pool 的使用是先close(),在join(),记住就行了
pool_obj.close()
pool_obj.join() print('ending')
进程池中有以下几个方法:
1.apply:从进程池里取一个进程并执行
2.apply_async:apply的异步版本
3.terminate:立刻关闭线程池
4.join:主进程等待所有子进程执行完毕,必须在close或terminate之后
5.close:等待所有进程结束后,才关闭线程池