今日学习目标
- 知道进程的代码实现,学习有关进程的知识点,对互斥锁理解运用
文章目录
学习内容
- 代码创建进程
- join方法
- 进程间数据默认隔离
- 进程对象相关属性和方法
- 僵尸进程与孤儿进程(纯理论)
- 守护进程
- 互斥锁(重要)
一、代码创建进程
创建进程的本质:在内存中申请一块内存空间用于运行相应的程序代码
创建进程的方式有哪些
1.鼠标双击桌面一个应用图标
2.代码创建
知识补充
-
multiprocessing模块
multiprocessing是Python的标准模块,它既可以用来编写多进程,也可以用来编写多线程。 -
Process 类
class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
1.Process模块用来创建子进程,是Multiprocessing核心模块,可以实现多进程的创建,启动,关闭等操作。
2.一般需要传入target目标函数,args函数的参数
创建方式一
from multiprocessing import Process
import time
def task(name):
print('%s is running' % name)
time.sleep(3)
print('%s is over' % name)
if __name__ == '__main__':
p = Process(target=task, args=('jason',)) # 创建一个进程对象
p.start() # 告诉操作系统创建一个新的进程
print('主进程')
强调:
不同的操作系统创建进程的要求不一样
- 在windows中创建进程是以导入模块的方式进行 所以创建进程的代码必须写在__main__子代码中否则会直接报错 因为在无限制创建进程
- 在linux和mac中创建进程是直接拷贝一份源代码然后执行 不需要写在__main__子代码中
创建方式二
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, username):
self.username = username
super().__init__()
def run(self):
print('你好啊 小姐姐',self.username)
time.sleep(3)
print('get out!!!',self.username)
if __name__ == '__main__':
p = MyProcess('tony')
p.start()
print('主进程')
进程实现并发
- 实现方法
将服务端中与客户端通信的代码封装成一个函数,之后每来一个客户端就创建一个进程专门做交互
服务端代码
import socket
from multiprocessing import Process
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
return server
# 将服务客户端的代码封装成函数(通信代码)
def talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
if __name__ == '__main__':
server = get_server()
while True:
sock, addr = server.accept()
p = Process(target=talk, args=(sock, ))
p.start()
客户端代码
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
client.send(b'hello big baby~')
data = client.recv(1024)
print(data.decode('utf8'))
二、join方法
1.用法
join()的作用: 在进程中可以阻塞主进程的执行, 直到等待子线程全部完成之后, 才继续运行主进程后面的代码
其实就是实现进程同步
2.举例
from multiprocessing import Process
import time
def task(name, n):
print(f'{name} is running')
time.sleep(n)
print(f'{name} is over')
if __name__ == '__main__':
p1 = Process(target=task, args=('jason', 1))
p2 = Process(target=task, args=('tony', 2))
p3 = Process(target=task, args=('kevin', 3))
start_time = time.time()
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
end_time = time.time() - start_time
print('主进程', f'总耗时:{end_time}') # 主进程 总耗时:3.015652894973755
# 如果是一个start一个join交替执行 那么总耗时就是各个任务耗时总和
三、进程间数据默认隔离
为什么进程之间数据不能共享
启两个程序 一个QQ,一个微信, 开启了两个进程.如果进程之间可以通信, 那QQ的进程是不是可以任意访问微信的数据. 这样会造成诸多问题. 所以,进程间理论上是不能通信的. 但是可以借助中间代理(队列)
验证数据隔离
判断主进程中x的值是否改变,没有改变则说明父进程与子进程之间数据隔离
from multiprocessing import Process
x=1000
def task():
global x # 引用成全局变量
x=2 # 修改全局变量( 由于数据隔离,修改的数据只存在子进程中)
if __name__ == '__main__':
p1=Process(target=task,)
p1.start()
# 如果x的值没有改变,说明父进程与子进程之间数据隔离
print(f'主进程中的x的变量:{x}')
四、进程对象相关属性和方法
常见的属性和方法
pid:获取子进程id
name:获取子进程name属性
terminate:杀死子进程
is_alive:查看子进程是否还活着
from multiprocessing import Process
import time
def task():
print('子线程 is running')
if __name__ == '__main__':
p=Process(target=task,name='任务1')
p.start()
print(p.pid) # 获取子进程的pid
print(p.name) # 获取子进程的name属性
p.terminate() # 杀死进程 :通知操作系统执行当前任务(耗时)
time.sleep(1) # 由于is_alive存在主进程中.所以需要睡1秒之后再进行查看
print(p.is_alive())
print('主进程')
获取进程以及父进程的pid
-
PID含义
在内存中开启多个进程,操作系统利用PID区分这些进程,每个进程都有一个唯一PID表示 -
获取方法
- 终端查看所有pid tasklist
- 指定具体的PID tasklist | findstr python
- 代码查看pid os 模块
- current_process函数()
from multiprocessing import Process, current_process
current_process().pid
# 利用os 模块 查看 pid(当前进程id) 和 ppid(主进程id)
import os
import time
print('子进程pid:',os.getpid()) # 获取当前进程的进程号
print('父进程pid:',os.getppid()) # 获取当前进程的主进程号
time.sleep(100)
五、僵尸进程与孤儿进程(纯理论)
进程运行了解
每个进程退出时,内核释放应该进程所有的资源,但是还会保留一部分的信息,如:进程号,进程状态,运行时间.直到主进程利用这个waitepid()方法,才能够完全释放,回收子进程的资源
僵尸进程
主进程不能正常的使用waitepid()方法对子进程进行资源回收,这个状态是僵尸状态(Z状态).那么这部分的子进程就会成为僵尸进程,每一次子进程结束前,都会有一个僵尸状态.直到父进程调用waitepid()才会从僵尸态到消亡态
僵尸进程的害处
僵尸进程的资源无法被释放,长期占用.内存压力增大.当新的程序来处理时,则会因为资源不足而导致运行失败.
孤儿进程
父进程已经挂掉了,子进程还在运行.这个子进程就是孤儿进程. 孤儿进程会被’init’进程回收
六、守护进程
简介
守护进程本身是一个子进程,守护的主进程,结束条件:主进程代码结束了,守护进程也就结束了
举例
from multiprocessing import Process
import time
def task(name):
print(f'大内总管:{name}正常活着')
time.sleep(3)
print(f'大内总管:{name}正常死了')
if __name__ == '__main__':
p = Process(target=task, args=('仆人',))
# 必须写在start前面
p.daemon = True # 将子进程设置为守护进程:主进程结束 子进程立刻结束
p.start()
print('主人寿终正寝')
总结
# 守护进程守护是什么?
是主进程的'代码运行完毕'. 守护进程就结束了.
# 为什么要有主进程等待子进程结束之后才结束呢?
因为主进程负责给子进程回收一些系统资源
# 个人理解: 主进程会等待所有的非守护进程的子进程执行完毕. 连同守护进程的资源统一回收.
七、互斥锁(重要)
简介
每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象(串行)
作用
来保证共享数据操作的完整性和安全性(文本数据),保证数据的公平性
与join的区别
共同点: 都能实现cpu的进程串行
不同点: join是人为指定顺序, 不能保证公平性. 互斥锁能够保证公平性
案例:模拟抢票
### db.json 自己提前创建好
with open('db.json', 'w', encoding='utf-8') as f:
dic={'count':1}
json.dump(dic, f)
### searc方法 打印剩余票数
def search():
time.sleep(random.random())
with open('db.json', encoding='utf-8') as f:
dic = json.load(f)
print(f'剩余票数:{dic["count"]}')
### 模拟多用户(多进程)抢票
def get():
with open('db.json', encoding='utf-8') as f:
dic = json.load(f)
time.sleep(random.randint(0, 2))
if dic['count'] > 0:
dic['count'] -= 1
with open('db.json', 'w', encoding='utf-8') as f:
json.dump(dic, f)
print(f'用户:{os.getpid()} ,购买成功~~')
else:
print(f'{os.getpid()} 没票了~~~~')
def task(lock):
search()
lock.acquire() #给抢票购买, 加锁 . 既保证了数据的安全性,也保证了数据公平性
get()
lock.release()# 解锁
if __name__ == '__main__':
lock = Lock()
for i in range(5):
p1 = Process(target=task, args=(lock,)) # 模拟5个用户进程
p1.start()