目录
2.在一个进程下开启多个线程与在一个进程下开启多个子进程的区别
一.进程锁
1.引入
锁在IT界都是非常重要的,不但在python中出现,尤其是数据库中的锁更多,比如:表锁,行锁,悲观锁,乐观锁,进程锁,互斥锁,递归锁,可重入锁,死锁等等
使用锁的目的就是为了安全
import time
from multiprocessing import Process, Lock
def task(i, lock):
# 上一把锁
lock.acquire()
print("进程%s来了" % i)
time.sleep(1)
print("进程%s走了" % i)
# 释放锁
lock.release()
"""只要你上了锁,一定别忘了最后释放锁,否则的话,别的进程永远进不来"""
# 加锁一定好码? 虽然保证了数据的安全,但是呢,执行的效率一定是降低了
# 有些场景该加锁的时候一定要加锁,
if __name__ == '__main__':
lock=Lock() # 得到一把锁
for i in range(3):
p = Process(target=task, args=(i+1, lock))
p.start()
2.如何查看进程号
import time
import os
from multiprocessing import Process, Lock
"""有了进程号,我们就可以通过进程号来结束进程的执行 kill 9176 kill -9 9176"""
# taskkill /pid {pid}
def task():
print("task进程的进程号:", os.getpid()) # os.getpid() 写在哪个进程里面就会输出哪个进程的进程号
print("task进程的父进程的进程号:", os.getppid()) # parent process
import time
time.sleep(20)
if __name__ == '__main__':
p=Process(target=task, )
p.start()
print('子进程的进程号:', p.pid)
print("主进程的进程号", os.getpid())
time.sleep(10)
3.进程之间数据隔离问题
n=100
def task():
global n
n=1
print("子进程")
from multiprocessing import Process
"""这两个进程之间的数据有没有通信? 么有通信"""
if __name__ == '__main__':
p = Process(target=task)
p.start()
"""先让子进程先执行,让子进程去改值"""
p.join()
print("主进程中得值:", n)
"""问题是:如何让进程与进程之间数据通信? """
总结:加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行修改,速度慢但是保证了数据的安全
虽然也可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享文件基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理
因此,我们最好寻找一种既能兼顾效率又能兼顾好锁问题的方案,这就是队列
二.队列
1.数据的结构有哪些
链表、单链表、双链表、循环链表、栈、队列、树、二叉树、图等
- 队列:先进先出
- 栈:先进后出
2.在python中如何使用队列
他给我们提供了一个内置的队列类
from multiprocessing import Queue
q=Queue(3)
## 入队:往队列里面添加数据
"""block=True, timeout=None"""
q.put('helloworld1')
q.put('helloworld2')
q.put('helloworld3')
# block=False: 如果往队列里面放数据放不进去的时候,会立马报错,报队列已满的错误信息
# q.put('helloworld4', block=False)
# timeout:超时的时间, 如果在指定的时间内,没有放进去,就会直接报错
# q.put('helloworld4', timeout=3)
# q.put_nowait('helloworld4')
## 出队:从队列里面取出值
print(q.get())
print(q.get())
print(q.get())
# print(q.get(block=False))
# print(q.get(timeout=3))
# print(q.get_nowait())
print(q.qsize())
print(q.empty())
print(q.full())
"""现在队列里面的数据在哪里存着? 在内存中存着的"""
# 专业的消息队列:kafka, rabbitmq等专业的消息队列 他们能够解决一些特殊场景的问题
三.生产者消费者模型
在并发编程里使用生产者消费者模式能够解决绝大多数并发问题,该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度
1.为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程,在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完才能继续生产数据,同理,如果消费者的处理能力大于生产者的生产能力,那么消费者就必须等待生产者,为解决这个问题才引入了生产者和消费者模式
def producer(q, food):
"""先给我生产10个包子"""
for i in range(10):
q.put("生产的第%s个包子" % i)
def consumer(q):
while True:
res = q.get()
print(res)
if res is None:
break
from multiprocessing import Process, Queue
if __name__ == '__main__':
q = Queue(20)
# 生产者
p1 = Process(target=producer, args=(q,'包子'))
p2 = Process(target=producer, args=(q,'粽子'))
p3 = Process(target=producer, args=(q,'豆浆'))
p4 = Process(target=producer, args=(q,'coffee'))
p1.start()
p2.start()
p3.start()
p4.start()
# 消费者
c1 = Process(target=consumer, args=(q,))
c2 = Process(target=consumer, args=(q,))
c1.start()
c2.start()
"""None放在这里是不行的,就想放在这里,你说怎么办?"""
p1.join()
p2.join()
q.put(None)
2.什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题,生产者和消费者彼此之间不直接通讯,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取数据,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
基于队列实现生产者消费者模型
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(q):
for i in range(10):
time.sleep(random.randint(1,3))
res='包子%s' %i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
if __name__ == '__main__':
q=Queue()
# 生产者们:即厨师们
p1=Process(target=producer,args=(q,))
# 消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
# 开始
p1.start()
c1.start()
print('主')
生产者消费者模型总结
# 生产者消费者模型总结
# 程序中有两类角色
一类负责生产数据(生产者)
一类负责处理数据(消费者)
# 引入生产者消费者模型为了解决的问题是:
平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度
# 如何实现:
生产者<-->队列<——>消费者
# 生产者消费者模型实现类程序的解耦和
此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后一直处于死循环中且卡在q.get()这一步
解决方法无非就是让生产者生产完毕后往队列中发送一个结束信号,这样消费者在接收到结束信号后就可以退出循环了
生产者在生产完毕后发送结束信号None
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
if res is None:break # 收到结束信号则结束
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(q):
for i in range(10):
time.sleep(random.randint(1,3))
res='包子%s' %i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
q.put(None) # 发送结束信号
if __name__ == '__main__':
q=Queue()
# 生产者们:即厨师们
p1=Process(target=producer,args=(q,))
# 消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
# 开始
p1.start()
c1.start()
print('主')
注意:结束信号None,不一定要由生产者发送,主进程里同样可以发,但主进程需要等待生产者结束后才应该发送信号
主进程在生产者生产完毕后发送结束信号None
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
if res is None:break #收到结束信号则结束
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(q):
for i in range(2):
time.sleep(random.randint(1,3))
res='包子%s' %i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
if __name__ == '__main__':
q=Queue()
# 生产者们:即厨师们
p1=Process(target=producer,args=(q,))
# 消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
# 开始
p1.start()
c1.start()
p1.join()
q.put(None) #发送结束信号
print('主')
但上述解决方式在有多个生产者和消费者时,我们则需要用一个很重复的方式去解决:有几个消费者就发送几次结束信号
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
if res is None:break # 收到结束信号则结束
time.sleep(random.randint(1,3))
print('\033[45m %s 吃 %s \033[0m' %(os.getpid(),res))
def producer(name,q):
for i in range(2):
time.sleep(random.randint(1,3))
res='%s%s' %(name,i)
q.put(res)
print('\033[44m %s 生产了 %s\033[0m' %(os.getpid(),res))
if __name__ == '__main__':
q=Queue()
# 生产者们:即厨师们
p1=Process(target=producer,args=('包子',q))
p2=Process(target=producer,args=('豆腐',q))
p3=Process(target=producer,args=('油条',q))
# 消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
# 开始
p1.start()
p2.start()
p3.start()
c1.start()
p1.join() # 必须保证生产者全部生产完毕,才应该发送结束信号
p2.join()
p3.join()
q.put(None) # 有几个消费者就应该发送几次结束信号None
q.put(None) # 发送结束信号
print('主')
其实我们的思路无非就是发送几次信号而已,有另外一种队列提供了这种机制
# JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
# 参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
# 方法介绍:
JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q):
while True:
res=q.get()
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走了
def producer(name,q):
for i in range(10):
time.sleep(random.randint(1,3))
res='%s%s' %(name,i)
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
q.join()
if __name__ == '__main__':
q=JoinableQueue()
# 生产者们:即厨师们
p1=Process(target=producer,args=('包子',q))
p2=Process(target=producer,args=('豆腐',q))
p3=Process(target=producer,args=('油条',q))
# 消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
c1.daemon=True
c2.daemon=True
# 开始
p_l=[p1,p2,p3,c1,c2]
for p in p_l:
p.start()
p1.join()
p2.join()
p3.join()
print('主')
# 主进程等--->p1,p2,p3等---->c1,c2
# p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据
# 因而c1,c2也没有存在的价值了,应该随着主进程的结束而结束,所以设置成守护进程
四.线程
在一个进程中,线程是必须存在的,至少要有一个线程来执行任务
一个进程中可以有多个线程,在一个进程中可有开启多个线程来执行任务
进程和线程都是有操作系统调度的
进程是操作系统分配资源的基本单位,线程是操作系统执行的最小单位
1.如何开启线程
(跟进程的开启是一样的)
方式一:
#方式一
from threading import Thread
import time
def sayhi(name):
time.sleep(2)
print('%s say hello' %name)
if __name__ == '__main__':
t=Thread(target=sayhi,args=('egon',))
t.start()
print('主线程')
方式二:
# 方式二
from threading import Thread
import time
class Sayhi(Thread):
def __init__(self,name):
super().__init__()
self.name=name
def run(self):
time.sleep(2)
print('%s say hello' % self.name)
if __name__ == '__main__':
t = Sayhi('kevin')
t.start()
print('主线程')
2.在一个进程下开启多个线程与在一个进程下开启多个子进程的区别
(1)谁的开启速度快
from threading import Thread
from multiprocessing import Process
import os
def work():
print('hello')
if __name__ == '__main__':
# 在主进程下开启线程
t=Thread(target=work)
t.start()
print('主线程/主进程')
'''
打印结果:
hello
主线程/主进程
'''
# 在主进程下开启子进程
t=Process(target=work)
t.start()
print('主线程/主进程')
'''
打印结果:
主线程/主进程
hello
'''
(2)看一看进程号:
from threading import Thread
from multiprocessing import Process
import os
def work():
print('hello',os.getpid())
if __name__ == '__main__':
# part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
t1=Thread(target=work)
t2=Thread(target=work)
t1.start()
t2.start()
print('主线程/主进程pid',os.getpid())
# part2:开多个进程,每个进程都有不同的pid
p1=Process(target=work)
p2=Process(target=work)
p1.start()
p2.start()
print('主线程/主进程pid',os.getpid())
(3)同一进程内的线程共享该进程的数据:
from threading import Thread
from multiprocessing import Process
import os
def work():
global n
n=0
if __name__ == '__main__':
# n=100
# p=Process(target=work)
# p.start()
# p.join()
# print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
n=1
t=Thread(target=work)
t.start()
t.join()
print('主',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据
五.守护进程
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常
注意:进程之间是相互独立的,主进程代码运行结束,守护进程随即终止
from multiprocessing import Process
import time
import random
class Eat(Process):
def __init__(self,name):
self.name=name
super().__init__()
def run(self):
print('%s is Eating' %self.name)
time.sleep(random.randrange(1,3))
print('%s is Eat end' %self.name)
p=Eat('kevin')
p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
p.start()
print('主')
迷惑人的例子
# 主进程代码运行完毕,守护进程就会结束
from multiprocessing import Process
from threading import Thread
import time
def foo():
print(123)
time.sleep(1)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
p1=Process(target=foo)
p2=Process(target=bar)
p1.daemon=True
p1.start()
p2.start()
print("main-------") # 打印该行则主进程代码结束,则守护进程p1应该被终止,可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止