Python学习day36-并发编程(2)
Process的一些常用方法和属性
join
join的主要用法是在于可以让主进程等待子进程运行完后,再执行下面的语句.要注意的一点是,主进程在等待,但是子进程在正常运行.
下面是join一些简单用法,包括串行,并行以及并行的优化
xxxxxxxxxx
from multiprocessing import Process
import time
def foo():
print('进程start')
time.sleep(2)
print('进程end')
if __name__ == '__main__':
p = Process(target=foo)
p.start()
time.sleep(5)
p.join() # 要等待该子进程全部执行完才会继续向下运行,主进程在等待,子进程在运行,内部会调用wait(),回收其子进程的pid号
print('主')
# 输出结果为:
# 进程start
# 进程end
# 主
# 串行,开辟内存空间要多费时间
def foo(x):
print(f'{x}进程start')
time.sleep(x)
print(f'{x}进程end')
if __name__ == '__main__':
p = Process(target=foo, args=(3,))
p2 = Process(target=foo, args=(2,))
p3 = Process(target=foo, args=(1,))
start = time.time()
p.start()
p.join()
p2.start()
p2.join()
p3.start()
p3.join()
# time.sleep(5)
end = time.time()
print(f'{end-start}')
print('主')
# 结果为:
# 3进程start
# 3进程end
# 2进程start
# 2进程end
# 1进程start
# 1进程end
# 6.7584381103515625这个时间不是绝对的,会根据所在机器的性能变化
# 主
# 并行,真正的同时运行
def foo(x):
print(f'{x}进程start')
time.sleep(x)
print(f'{x}进程end')
if __name__ == '__main__':
p = Process(target=foo, args=(10,))
p2 = Process(target=foo, args=(2,))
p3 = Process(target=foo, args=(1,))
start = time.time()
p.start()
p2.start()
p3.start()
# time.sleep(5)
p.join()
p2.join()
p3.join()
end = time.time()
print(f'{end-start}')
print('主')
# 结果为:
# 2进程start
# 3进程start
# 1进程start
# 1进程end
# 2进程end
# 3进程end
# 3.3003978729248047
# 主
# 上述这种并行的发生看起来非常的麻烦,所以我们其实可以用一个循环把下面重复性的创建进程以及join暂停进程都放到循环里,简化代码.
# 并行的优化
def foo(x):
print(f'{x}进程start')
time.sleep(x)
print(f'{x}进程end')
if __name__ == '__main__':
start = time.time()
p_list = []# 创建一个p_list列表用来接收创建的子进程
for i in range(1, 4):
p = Process(target=foo, args=(i,))
p.start()
p_list.append(p)
for p in p_list:#将列表里的子进程一一拿出来循环,然后通过join分别暂停主进程,从而达到并行的目的
p.join()
end = time.time()
print(end - start)
print('主')
# 结果为:
# 2进程start
# 1进程start
# 3进程start
# 1进程end
# 2进程end
# 3进程end
# 3.275794744491577
# 主
获取当前进程pid和父进程pid
获取当前进程pid的常用方式有两种,分别是:
os.getpid(),这是os模块所带有的方法,可以查看当前进程的pid号
os.getppid(),注意这里是ppid,多了一个p,是查看当前进程的父进程的pid号
子进程对象.pid
xxxxxxxxxx
from multiprocessing import Process, current_process # 获取当前进程的对象
import time, os
def task():
print('子进程start')
print('在子进程中查看自身的pid', current_process().pid)
print('在子进程中查看自身的pid', os.getpid())
print('在子进程中查看父进程的pid', os.getppid())
time.sleep(200)
print('子进程end')
if __name__ == '__main__':
p = Process(target=task)
p.start()
print('在主进程查看子进程的pid', p.pid) # 一定写在子进程启动之后,即start之后
print('主进程pid', os.getpid())
print('主')
# 运行结果为:(这里pid每次执行都是不一样的,都是重新分配的)
# 在主进程查看子进程的pid 9964
# 主进程pid 11584
# 主
# 子进程start
# 在子进程中查看自身的pid 9964
# 在子进程中查看自身的pid 9964
# 在子进程中查看父进程的pid 11584
name和is_alive
name() 进程名称,可以手动定义
is_alive() 判断进程是否还存在,只要运行完了就返回false,即便是僵尸进程也是false
xxxxxxxxxx
from multiprocessing import Process, current_process
import time
def foo():
print('进程start')
print(current_process().name)
time.sleep(2)
print('进程end')
if __name__ == '__main__':
p = Process(target=foo, name='nick')
# p2 = Process(target=foo, name='jack')
# p3 = Process(target=foo, name='mata')
p.start()
# p2.start()
# p3.start()
print(p.is_alive())# 判定进程是否还活着,返回值是True活着False
print(p.name)# 输出进程的名字,也可以是自身赋予的值,如果自己没有赋值系统默认的就是Process-数字
# print(p2.name)
# print(p3.name)
# 结果是:
# True
# nick
# 进程start
# nick
# 进程end
terminate
手动结束子进程,但其实只是给操作系统发送了一个关闭进程的请求,需要时间才能完成,推荐在后面加上p.join()来实时判断进程是否已经结束了,也就是说进程只有执行完了才会进行下面的语句.
xxxxxxxxxx
from multiprocessing import Process, current_process
import time
def foo():
print('进程start')
print(current_process().name)
time.sleep(2)
print('进程end')
if __name__ == '__main__':
p = Process(target=foo, name='nick')
p.start()
p.terminate()
# p.join()
print(p.is_alive())
# print(p.name)
daemon
守护进程(本质就是一个子进程)
x
from multiprocessing import Process, current_process
import time
def foo():
print('守护进程start', time.time())
time.sleep(3)
print('守护进程end', time.time())
def task():
print('子进程start', time.time())
time.sleep(5)
print('子进程end', time.time())
if __name__ == '__main__':
p = Process(target=foo, name='nick')
p2 = Process(target=task, name='nick')
p.daemon = True # 定义为守护进程,必须要在start之前定义
p.start()
p2.start()
time.sleep(2)
print('主', time.time())
# 结果为:
# 守护进程start 1568278460.6168563
# 子进程start 1568278460.6188455
# 主 1568278462.376258
# 子进程end 1568278465.6189692
# 可以看到上述例程的结果里没有守护进程结束的输出语句,因为父进程已经结束了,所以守护进程跟随父进程结束了,后面的就没有打印.
Tips:抢票软件的原理
先来语言分析一波,抢票的总体流程是怎样的,12306的数据库里肯定有实时的票数以及票价,我们购买的时候呢,就要发一个请求,包括查询和购买都要发请求,然后12306的服务器给我们返回数据库里的票数,可是我们都知道,网络是有延迟的,数据传输也是需要时间的,所以很可能同时有很多人,同时查询这个车次的票,然后得到还有一张票的消息(比如),然后这些人都点击购买,那么12306会把这个票给哪个用户呢?
其实这种情况下我们如果用并行进程的概念是行不通的,因为延迟的情况下就有可能很多人都买到了这张票,可是实际情况是不可能的,所以这里为了数据的安全以及逻辑的缜密,我们就要牺牲效率,转而用串行的方法来处理这个事情,尽管效率会慢很多,但是不会出现一张票同时卖给很多人的情况.也就是说用join方法来实现串行.
例程如下:
x
# 这里我们需要在文件的同目录创建一个db.txt文件
# 在文件里写{"count": 1}
from multiprocessing import Process, current_process
import time
import json
import os
def search():
time.sleep(1) # 模拟网络io
with open('db.txt', 'rt', encoding='utf-8')as f:
res = json.load(f)
print(f'还剩{res["count"]}张票')
def get():
with open('db.txt', 'rt', encoding='utf-8')as f:
res = json.load(f)
print(f'还剩{res["count"]}张票')
time.sleep(1) # 模拟网络io
if res['count'] > 0:
res['count'] -= 1
with open('db.txt', 'wt', encoding='utf-8')as f:
json.dump(res, f)
f.flush()
print(f'{os.getpid()}抢票成功')
time.sleep(1) # 模拟网络io
else:
print('票已经售空!~!~!~!~!~!~!~!')
# current_process().terminate()
def task():
#search()
get()
if __name__ == '__main__':
for i in range(5):
p = Process(target=task)
p.start()
p.join()
# 输出结果为:
# 还剩1张票
# 11576抢票成功
# 还剩0张票
# 票已经售空!~!~!~!~!~!~!~!
# 还剩0张票
# 票已经售空!~!~!~!~!~!~!~!
# 还剩0张票
# 票已经售空!~!~!~!~!~!~!~!
# 还剩0张票
# 票已经售空!~!~!~!~!~!~!~!
当然实际的抢票软件肯定做了更多的优化,不会这么简单,但是大概逻辑是差不多的.