Python攻城师的成长————进程(join方法、僵尸进程与孤儿进程、守护进程、互斥锁)

今日学习目标

  • 知道进程的代码实现,学习有关进程的知识点,对互斥锁理解运用


学习内容

  • 代码创建进程
  • 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('主进程')

强调:
不同的操作系统创建进程的要求不一样

  1. 在windows中创建进程是以导入模块的方式进行 所以创建进程的代码必须写在__main__子代码中否则会直接报错 因为在无限制创建进程
  2. 在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表示

  • 获取方法

    1. 终端查看所有pid tasklist
    2. 指定具体的PID tasklist | findstr python
    3. 代码查看pid os 模块
    4. 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()
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值