python网络编程--进程(方法和通信),锁, 队列,生产者消费者模型

时间:2023-03-08 16:45:14
1.进程

正在进行的一个过程或者说一个任务.负责执行任务的是cpu

进程(Process:
  是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。我们自己在python文件中写了一些代码,这叫做程序,运行这个python文件的时候,这叫做进程。

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
   
Process([group [, target[, name [, args [,kwargs]]]]])由该类实例化的对象,可用来开启一个子进程
      开启进程:
from multiprocess import Process
p = Process(target=方法名)
 
  参数介绍:
    group未使用,值始终为None
target表示调用对象,即子进程要执行的任务
args表示调用对象的位置参数元组, args(1,)
kwargs表示调用对象的字典,kwargs = {'name':'bob'}
name为子进程的名称
1.进程的创建方法,两种(引入multiprocess模块的Process模块)

  1).函数的方式
import time
from multiprocessing import Process def f1():
time.sleep(3)
print('xxxx') def f2():
time.sleep(3)
print('ssss') # f1()
# f2() #windows系统下必须写main,因为windows系统创建子进程的方式决定的,开启一个子进程,这个子进程 会copy一份主进程的所有代码,并且机制类似于import引入,这样就容易导致引入代码的时候,被引入的代码中的可执行程序被执行,导致递归开始进程,会报错
if __name__ == '__main__': #
# p1 = Process(target=f1,)
p2 = Process(target=f2,)
# p1.start()
p2.start()

  2).面向对象的创建方式
from multiprocessing import Process
class MyProcess(Process): def __init__(self,n):
super().__init__() #别忘了执行父类的init
self.n = n def run(self):
print('这里是参数%s' %self.n) if __name__ == '__main__': p1 = MyProcess('我的进程')
p1.start()


2.for循环创建进程
import time
from multiprocessing import Process def f1(i):
time.sleep(3)
print(i) if __name__ == '__main__': for i in range(20):
p1 = Process(target=f1,args=(i,))
p1.start()

 3.进程的两种传参方式

from multiprocessing import Process

#演示两种传参方式
def f1(n):
print(n) if __name__ == '__main__':
# p1 = Process(target=f1,args=('参数1',)) #创建进程对象
p1 = Process(target=f1,kwargs={'n':'参数2'}) #创建进程对象 p1.start() #给操作系统发送了一个创建进程的信号,后续进程的创建都是操作系统的事儿了

  

2.进程的其他方法(Process)
  
1.join方法    在主进程运行过程中如果想并发的执行其他任务,我们可以开启子进程,此时主进程的任务与子进程的任务分为两种情况     情况1:
      在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕之后,主进程还需要等待子进程执行完毕,然后统一回收资源
    
    情况2:
      如果主进程的任务在执行到某一个阶段时,需要等待子进程执行完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用
  
示例:
import time
from multiprocessing import Process def f1():
time.sleep(2)
print('xxxx') def f2():
time.sleep(2)
print('ssss') # f1()
# f2()
if __name__ == '__main__': p1 = Process(target=f1,)
p1.start()
p1.join() # 主进程等待子进程运行完才继续执行 print('开始p2拉') p2 = Process(target=f2,)
p2.start()
p2.join()
print('我要等了...等我的子进程...')
# time.sleep(3)
print('我是主进程!!!')

  

2.Process类中其他的方法

   方法:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True
p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
    属性:  
p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
p.name:进程的名称
p.pid:进程的pid
p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
 
import time
import os
from multiprocessing import Process def f1():
print('子进程的pid',os.getpid())
print('子进程的父进程的pid',os.getppid())
print('aaa') def f2():
print('bbb') if __name__ == '__main__': p1 = Process(target=f1,name='宝宝1')
p2 = Process(target=f2,)
p1.start()
p2.start()
print(p1.name)
print('子进程的pid',p1.pid)
print('父进程的id',os.getpid()) #进程的其他方法
def f1():
time.sleep(5)
print('子进程1号') if __name__ == '__main__':
p = Process(target=f1,)
p.start() print(p.is_alive()) #判断子进程是否还活着,是否还在运行
p.terminate() #给操作系统发送一个结束进程的信号
time.sleep(0.5)
print(p.is_alive())

   验证进程之间的空间是相互隔离的:

from multiprocessing import Process

num = 100

def f1():
global num
num = 3
print('子进程中的num',num) print('>>>>>',num)
if __name__ == '__main__':
p = Process(target=f1,)
p.start()
p.join()
print('主进程中的num',num)

  

3.守护进程

主进程结束,守护进程也跟着结束,对于进程来说,主进程代码结束就是结束 p.daemon:
    默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随即终止,并且设定为True之后,p不能创建字节的新进程,必须在p.start()之前设置
    1.守护进程会在主进程代码执行结束后就终止

    2.守护进程内再无法开启子进程,否则抛出异常:
 AssertionError: daemonic processes are not allowed to have children
    如果我们有两个任务需要并发执行,那么开一个主进程和一个子进程分别去执行就可以了,如果子进程的任务在主进程任务结束后就没有存在的必要了,那么该子进程应该在开启前就被设置成守护进程.主进程代码运行结束,守护进程随即终止
  守护进程示例:
import time
from multiprocessing import Process def f1():
time.sleep(3)
print('xxxx') def f2():
time.sleep(5)
print('普通子进程的代码')
if __name__ == '__main__':
p = Process(target=f1,)
p.daemon = True #将该进程设置为守护进程,必须写在start之前,意思如果我的主进程代码运行结束了,你这个子进程不管运行到什么地方,都直接结束
p.start() #开启一个普通的子进程来验证一下守护进程的结束只和主进程的代码运行结束有关系,而整个程序的结束需要主进程和普通的子进程的代码都运行结束才结束
p2 = Process(target=f2,)
p2.start()
#等待2号普通进程的结束,才继续执行下面主进程中的代码
# p2.join()
#守护进程会跟跟着父进程的代码运行结束,就结束
print('主进程结束')

  4.进程锁/互斥锁

进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端没有问题.但共享带来竞争,进而导致错乱,这时就需要加锁处理.互斥锁的原理就是把病发改成串行,降低了效率,但保证了数据安全不错乱.

        互斥锁与join()
  join()将一个任务整体串行,互斥锁的好处就是可以将一个任务中的某一段代码串行.
    loc = Lock()

    #第一种方式
def func(loc):
loc.acquire()
需要锁的代码
loc.release()
#第二种方式
With loc:
需要锁的代码

5.进程间的数据共享
 通过引入manager,结合Lock来实现进程之间的数据共享

   1.使用for循环来创建子进程
import time
from multiprocessing import Process def f1():
time.sleep(0.5)
print('xxx') if __name__ == '__main__':
p_list = []
#for循环创建子进程,并且完成主进程等待所有子进程执行结束,才继续执行
for i in range(10):
p = Process(target=f1,)
p.start()
p_list.append(p)
p.join()
# for pp in p_list:
# pp.join() print('主进程结束')

  2.数据共享

    为了保证进程间使用数据的安全,我们对数据处理时经常这样:

a = 10
tmp = a
tmp -= 1
a = tmp
a -= 1 # a = a - 1

    示例:

def f1(m_d,l2):
# m_d['num'] -= 1 #
with l2:
# l2.acquire()
tmp = m_d['num']
tmp -= 1
time.sleep(0.1)
m_d['num'] = tmp
# l2.release() if __name__ == '__main__':
m = Manager()
l2 = Lock()
m_d = m.dict({'num':100})
p_list = []
for i in range(10):
p = Process(target=f1,args=(m_d,l2))
p.start()
p_list.append(p) [pp.join() for pp in p_list] print(m_d['num'])

  

6.队列

   进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种消息之间都是使用消息传递的

    1.创建队列的类(底层就是管道和锁定的方式实现)
 Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递
    2.参数介绍:
maxsize是队列中允许的最大项数,省略则无大小限制 需要明确:
1.队列中存放的是消息而非大数据
2.队列占用的是内存空间,因而maxsize即便是无大小限制也受限于内存大小 3.主要方法介绍:
q.put()方法用来插入数据到队列中
q.get()方法可以从队列读取并且删除一个元素
        q = Queue(5)
q.put() #满了会等待
q.get() #没有数据了会等待
q.qsize()
q.empty() 不可靠
q.full()不可靠
q.get_nowait() #不等待,但是报错
q.put_nowait() #不等待,但是报错
   基于队列的进程间通信
from multiprocessing import Process,Queue

def f1(q):
q.put('约吗?') if __name__ == '__main__':
q = Queue(3) p = Process(target=f1,args=(q,))
p.start() son_p_msg = q.get() print('来自子进程的消息:',son_p_msg)
7.生产者消费者模型

1.程序中有两类角色
  一类负责生产数据(生产者)
   一类负责处理数据(消费者) 2.引入生产者消费者模型为了解决的问题是
  平衡生产者与消费者之间的速度差
  程序解开耦合 3.如何实现生产者消费者模型
生产者<--->队列<--->消费者   通过队列实现一个生产者<--->消费者模型,基于joinnamlequeue的在下面,这里只是呈现基本模型:   生产包子---> 吃包子
import time
from multiprocessing import Process,Queue #生产者
def producer(q):
for i in range(10):
time.sleep(0.7)
s = '大包子%s号'%i
print(s+'新鲜出炉,拿去用')
q.put(s) def consumer(q):
while 1:
time.sleep(1) baozi = q.get()
print(baozi+'被吃了') if __name__ == '__main__':
q = Queue(10) pro_p = Process(target=producer,args=(q,))
con_p = Process(target=consumer,args=(q,))
pro_p.start()
con_p.start()

 8.进程队列:JoinableQueue([maxsize])

    这就像一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理.通知进程是使用共享的信号和条件变量来实现的

    maxsize是队列中允许最大项数,省略则无大小限制

        q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理.如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常

        q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理.阻塞将持续到队列中的每个项目均
调用q.task_done()方法为止
  
 
 基于JoinableQueue来实现生产者消费者模型:
import time
from multiprocessing import Process,Queue,JoinableQueue
#生产者
def producer(q):
for i in range(10):
time.sleep(0.2)
s = '大包子%s号'%i
print(s+'新鲜出炉,拿去用')
q.put(s)
q.join() #就等着task_done()信号的数量,和我put进去的数量相同时,才继续执行
print('所有的任务都被处理了,继续潜行吧骚年们') def consumer(q):
while 1:
time.sleep(0.5)
baozi = q.get() print(baozi+'被吃了')
q.task_done() #给队列发送一个取出的这个任务已经处理完毕的信号 if __name__ == '__main__':
# q = Queue(30)
q = JoinableQueue(30) #同样是一个长度为30的队列 pro_p = Process(target=producer,args=(q,))
con_p = Process(target=consumer,args=(q,))
pro_p.start()
con_p.daemon = True
con_p.start() pro_p.join()
print('主进程结束')