进程
什么是进程?
进程:正在进行的一个过程或者说一个任务。而负责执行任务则是CPU。
进程与程序的区别
程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。
并发与并行
无论是并发还是并行,在用户看来都是【同时】运行的,无论是进程还是线程,都只是一个任务而已,真正干活的是CPU,CPU来做这些任务,而一个CPU同一时刻只能执行一个任务。
并发:伪并行,即看起来是同时运行。单个CPU+多道技术就可以实现并发。
并行:同时运行,只有具备多个CPU才能实现并行。
创建进程: 函数式(常用方法)
from multiprocessing import Process
import time
def fun():
print("我是fun函数")
def task(name):
print("{0}运行".format(name))
time.sleep(3)
fun()
print("{0}完成了".format(name))
if __name__ == '__main__':
p = Process(target=task, args=("马爷",))
p.start()
print("====主进程")
Python并发编程:多进程
验证进程之间的空间隔离
#例1:
from multiprocessing import Process
import time
x = 100
def task(name):
global x
x = 0
if __name__ == '__main__':
p = Process(target=task, args=("马爷",))
p.start()
print(x) # 此时x为100,因为主进程先执行了
# 例2
# 子进程先执行,主进程后执行通过打印输出验证空间是相互隔离的
from multiprocessing import Process
import time
x = 100
def task(name):
global x
x = 0
print("子进程x", x) # 子进程x=0
if __name__ == '__main__':
p = Process(target=task, args=("马爷",))
p.start()
time.sleep(3)
print(x) # 此时x为100
# 例3
from multiprocessing import Process
import time
x = []
def task(name):
x.append(100)
print("子进程x", x) # 子进程[100]
if __name__ == '__main__':
p = Process(target=task, args=("马爷",))
p.start()
time.sleep(3)
print(x) # 此时x为[]
join的使用
join让父进程等待子进程结束完,父进程才可以结束
例1:开启一个子进程
from multiprocessing import Process
import time
x = 1000
def task(name):
global x
x = 0
print("子进程x", x) # 子进程[100]
if __name__ == '__main__':
p = Process(target=task, args=("马爷",))
p.start()
p.join() # 父进程在原地等待
print("父进程", x) # 此时x为[]
例2:开启多个子进程
from multiprocessing import Process import time x = 1000 def task(num): global x x = 0 print("子进程{0}!".format(num)) if __name__ == '__main__': for i in range(10): p = Process(target=task, args=(i,)) p.start() print("主进程结束了")
打印输出:
主进程结束了
子进程0!
子进程1!
子进程2!
子进程3!
子进程4!
子进程6!
子进程7!
子进程8!
子进程5!
子进程9!
为什么主进程在子进程前面执行了?start只是向操作系统发出命令,而不是时马上开始,操作系统还需要从内存在开空间,开进程,进程开闭的损耗是很大的。所有主进程不会最后执行。
开启三个进程测试:
from multiprocessing import Process
import time
x = 1000
def task(num):
print("子进程开始了{0}".format(num))
time.sleep(num)
if __name__ == '__main__':
p1 = Process(target=task, args=(1,))
p2 = Process(target=task, args=(2,))
p3 = Process(target=task, args=(3,))
# 发三个信号,启动3个进程 6秒还是3秒
p1.start()
p2.start()
p3.start()
print("主进程结束了")
3个进程运行3秒左右。
精简版:
from multiprocessing import Process
import time
def task(num):
print("子进程开始了{0}".format(num))
time.sleep(num)
if __name__ == '__main__':
start_time = time.time()
p_l1 = []
for i in range(1, 4):
p = Process(target=task, args=(i,))
p_l1.append(p)
p.start()
for i in p_l1:
i.join()
print("主进程开始了{0}!".format(time.time() - start_time))
# 输出:主进程开始了3.159229278564453!
僵尸进程与孤儿进程
由于子进程没有结束,所有主进程需要等待子进程结束之后才可以结束。
为什么主进程不结束?
主进程要监听子进程状态变化,等子进程结束之后的一段时间内,对其回收。
由于主进程与子进程是异步的过程,主进程与子进程的开启与结束相互都没有关联,主进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么父进程则无法获取子进程的
状态信息。那么如何保证主进程可以在任意时刻获取子进程的结束时的状态呢?
unix提供了一个机制,在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号,退出状态,运行时间等)直到父进程通过wait/waitpid来获取时才释放。
什么是僵尸进程?
所有的子进程结束之后,在被父进程通过wait/waitpid回收之前,都称为僵尸进程(通过命令获取其运行状态为Z)。
僵尸进程的危害?如果主进程不调用wait/waitpid的话,那么保留的那段信息就不会释放,其进程号一直被占用,但是系统所能使用的进程号是有限的,如果产生大量的僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,这就是僵尸进程的危害。
什么是孤儿进程?
孤儿进程,一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
僵尸进程如何解决?
例如有个进程,它定期产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短。但是父进程只管生成新的子进程,至于子进程退出之后的事情,一概不问。这样,系统运行一段时间之后,系统中就会存在很多的僵尸进程。严格来说,僵尸进程并不是问题的根源,罪魁祸首是产生大量僵尸进程的那个父进程,把该父进程杀掉。那么它产生的僵尸进程就变成了孤儿进程,孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源。
守护进程
守护进程,例如:皇帝是太监的守护,皇帝如果死了,一般老太监也跟着自杀了。
主进程死了,守护进程也跟着死掉了。
将子进程设置成守护进程,它会等待主进程结束之后,马上结束:
from multiprocessing import Process
import time
def task(name):
print("{0}运行了".format(name))
time.sleep(5)
print("{0}完成了".format(name))
if __name__ == '__main__':
p = Process(target=task, args=("马爷",), name="任务1")
p.daemon = True # 子进程设置为守护进程
p.start()
print("===主进程")
# 输出:===主进程
结果主进程结束后马上结束打印输出:===主进程
互斥锁Lock
join和Lock共同点:都是将并发变成串行,从而保证有序进行。
并发时我们需要输出结果是有序的,效率第二。
这个时候使用互斥锁让进行平等竞争。
例如:
模拟三个员工同时向打印机发出请求,打印三份资料。
这个时候效率第二,输出结果变成串行,打印机需要先打印第一个人的结果,然后在打印第二个结果,第三个结果;
谁先到达谁先开始,通过Lock锁来完成,三个员工也就是三个进程谁先抢到Lock谁先执行。
例:通过Lock锁让tasek1执行完毕后,再执行task2,task3
from multiprocessing import Process from multiprocessing import Lock import time import random def task1(lock): lock.acquire() # 获得锁 print("task1开始执行") time.sleep(random.randint(1, 3)) print("task1:执行完毕") lock.release() # 释放锁 def task2(lock): lock.acquire() print("task2开始执行") time.sleep(random.randint(1, 3)) print("task2:执行完毕") lock.release() def task3(lock): lock.acquire() print("task3开始执行") time.sleep(random.randint(1, 3)) print("task3:执行完毕") lock.release() if __name__ == '__main__': mutex = Lock() p1 = Process(target=task1, args=(mutex,)) p2 = Process(target=task2, args=(mutex,)) p3 = Process(target=task3, args=(mutex,)) # 锁必须是同一个,主进程创建一个共同锁,然后通过参数的形式传递进取 p1.start() p2.start() p3.start()
打印输出:
task1开始执行
task1:执行完毕
task2开始执行
task2:执行完毕
task3开始执行
task3:执行完毕
抢票系统
需求:
用互斥锁模拟12306抢票系统10个人同时抢票,10个人都有查票的功能,然后谁先开始买票时,其他人只能等待,先查票,在买票。进程之间理论上来说是不可以共享内存数据的,但是可以共用一个硬盘,所以我们的数据应该放在一个文件中。
from multiprocessing import Process from multiprocessing import Lock import time import random import json import os def search(): """查车票""" time.sleep(random.randint(1, 3)) # 先模拟一下延迟效果 with open("db.json", encoding="utf-8") as f1: dic = json.load(f1) print("剩余票数为:{0}".format(dic.get("count"))) def get(): with open("db.json", encoding="utf-8") as f1: dic = json.load(f1) if dic.get("count") > 0: dic['count'] -= 1 time.sleep(random.randint(1, 3)) with open("db.json", "w+", encoding="utf-8") as f2: json.dump(dic, f2) print("{0}购票成功".format(os.getpid())) def task(lock): search() # 查询 lock.acquire() get() # 买票 lock.release() if __name__ == '__main__': mutex = Lock() for i in range(10): p = Process(target=task, args=(mutex,)) p.start()
db.json
{"count": 5}
将查票变成并发,但是抢票就是串行了,谁先付款谁先买票。互斥锁比join的优势,join是将所有进程变成串行并且必须按照你之前规定的顺序。互斥锁也是按照顺序但是是随机的顺序谁先开启进程,谁先加入。
所以修改数据的时候必须要串着来,互斥锁可以让一部分代码(修改共享数据的部分)串行,join只能让代码整体串行。