Queue模块

Queue模块可以实现队列的功能,先进先出。

实操:

from multiprocessing import Queue  # 导入
q = Queue(3) # 自定义队列的长度
# 朝队列中存放数据
q.put(111)
q.put(222)
# 用于判断队列是否满了
print(q.full()) # 输出:False
q.put(333)
print(q.full()) # 输出:True
"""如果队列满了继续存放数据会原地阻塞等待队列中出现空位"""
print(q.get()) # 输出:111
print(q.get()) # 输出:222
# 判断队列是否空了
print(q.empty()) # 输出:False
print(q.get()) # 输出:333
print(q.empty()) # 输出:True
"""如果队列空继续取数据会原地阻塞等待队列中出现数据"""
print(q.get_nowait()) # 功能与get一致,但队列中如果没有值,直接报错
q.put_nowait(123) # 功能与put一致,但队列中如果没有空位,直接报错

总结:

  • get():取值。
  • get_nowait():取值,但如果没有值就会报错。
  • put():存值。
  • put_nowait():存值,但如果队列满了就会报错。
  • empty():判断队列是否为空。
  • full():判断队列是否满了。

IPC机制(进程间通信)

IPC机制指的是两个进程之间进行数据通信的过程,可以是主进程与子进程数据交互,也可以是两个子进程数据交互,但本质都是一样的:不同内存空间中的进程数据交互。

我们可以用Queue队列模块实现IPC机制。

from multiprocessing import Process, Queue

def producer(q):
for i in range(3):
q.put(i)
print('子进程producer向队列中添加值:', i) def consumer(q):
for i in range(3):
print('子进程consumer从队列中取值:', q.get()) if __name__ == '__main__':
q = Queue(1)
# 创建往队列中添加值的进程,并把队列对象当参数传入
p = Process(target=producer, args=(q,))
# 创建从队列中取值的进程,并把队列对象当参数传入
p1 = Process(target=consumer, args=(q,))
p.start()
p1.start()

执行结果:

子进程producer向队列中添加值: 0
子进程consumer从队列中取值: 0
子进程producer向队列中添加值: 1
子进程consumer从队列中取值: 1
子进程producer向队列中添加值: 2
子进程consumer从队列中取值: 2

生产者消费者模型

生产者即负责生产/制作数据,消费者即负责消费/处理数据。

比如刚刚用Queue队列模块实现IPC机制中,producer方法即是生产者(往队列里添加值),consumer方法即是消费者(从队列中取值)。

遇到该模型需要考虑的问题其实就是供需平衡的问题,生产力与消费力要均衡,比如下面的代码中就是消费力等于生产力。

from multiprocessing import Process, Queue
import time
import random def producer(name, food, q):
for i in range(3):
data = f'{name}生产了第{i}个{food}'
print(data)
# 模拟生产过程
time.sleep(random.randint(1, 3))
q.put(food) def consumer(name, q):
# 写个死循环,确保队列数据全部取出
while True:
food = q.get()
# 判断是否把队列数据消费完了
if food is None: break
time.sleep(random.random())
print(f'{name}吃了一个{food}') if __name__ == '__main__':
q = Queue()
# 生产者一
p1 = Process(target=producer, args=('jason', '汉堡', q))
# 生产者二
p2 = Process(target=producer, args=('tom', '炸鸡', q))
# 消费者一
c1 = Process(target=consumer, args=('tony', q))
# 消费者二
c2 = Process(target=consumer, args=('david', q))
p1.start()
p2.start()
c1.start()
c2.start()
p1.join()
p2.join()
# None用于判断是否生产结束,有两个消费者所以要添加两个值
q.put(None)
q.put(None)

拓展:使用None用于判断是否生产结束具有不确定性,我们不知道生产者有几个时就无法使用了,所以我们这里可以使用JoinableQueue()创建队列,并修改部分代码

from multiprocessing import Process, JoinableQueue
import time
import random def producer(name, food, q):
for i in range(3):
data = f'{name}生产了第{i}个{food}'
print(data)
# 模拟生产过程
time.sleep(random.randint(1, 3))
q.put(food) def consumer(name, q):
# 写个死循环,确保队列数据全部取出
while True:
food = q.get()
# 判断是否把队列数据消费完了
if food is None: break
time.sleep(random.random())
print(f'{name}吃了一个{food}')
q.task_done() # 每次取数据都给队列反馈,让队列自己知道是否还有数据 if __name__ == '__main__':
# 使用JoinableQueue()创建队列
q = JoinableQueue()
# 生产者一
p1 = Process(target=producer, args=('jason', '汉堡', q))
# 生产者二
p2 = Process(target=producer, args=('tom', '炸鸡', q))
# 消费者一
c1 = Process(target=consumer, args=('tony', q))
# 消费者二
c2 = Process(target=consumer, args=('david', q))
p1.start()
p2.start()
# 给消费者添加守护进程,如果q.join()执行完了,那么消费者也该结束了
c1.daemon = True
c2.daemon = True
c1.start()
c2.start()
p1.join()
p2.join()
# 让队列全部数据取出才会往下执行
q.join()

线程理论

线程是需要依赖进程的,事实上,进程运行时并不会工作,而是线程在工作,进程只是在内存中申请了一块空间而已,所以进程是一个资源单位,而线程是执行单位;就相当于进程创建工厂,而线程是工厂的打工人。

开设线程的消耗远远小于进程,一个进程里至少有一个线程,也可以开设多个线程,创建线程无需申请内存空间,创建消耗资源小,并且一个进程内的多个线程数据是共享的。

创建线程的两种方式

创建线程与创建进程的方法几乎一致,并且创建线程无需在__main__下面编写,但是为了统一,还是习惯在子代码中写。

方式一:Thread()

from threading import Thread
import time def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over') if __name__ == '__main__':
t = Thread(target=task, args=('jason', ))
t.start() # 创建线程的开销极小 几乎是一瞬间就可以创建
print('主线程') """
执行结果:
jason is running
主线程
jason is over
"""

从执行结果来看,代码会先输出线程的内容,可见线程创建的迅速,如果是创建进程,一般情况下都是先输出主进程的内容。

方式二:重写Thread类的run方法

from threading import Thread
import time class MyThread(Thread):
def __init__(self, username):
super().__init__()
self.username = username
def run(self):
print(f'{self.username} jason is running')
time.sleep(3)
print(f'{self.username} is over') t = MyThread('jason')
t.start()
print('主线程') """
执行结果:
jason jason is running
主线程
jason is over
"""

线程实现TCP服务端的并发

线程实现TCP服务端的并发的方法与进程也几乎一致,唯一的区别就是线程节省资源。

服务端(Server)

import socket
from threading import Thread def task(sock):
while True:
msg = sock.recv(1024)
print(msg.decode('utf8'))
sock.send(b'from server') if __name__ == '__main__':
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
client, addr = server.accept()
p = Thread(target=task, args=(client, ))
p.start()

客户端(Client)

import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080)) while True:
s = input('发送给服务端的消息:')
send_msg = 'from client: %s' % s
client.send(send_msg.encode('utf8'))
msg = client.recv(1024)
print(msg.decode('utf8'))

线程join方法

join方法作用:让主线程代码等待子线程代码运行完毕之后再往下执行。

from threading import Thread
import time def task():
print('子线程 is running')
time.sleep(3)
print('子线程 is over') t = Thread(target=task)
t.start()
t.join()
print('主线程') """
执行结果:
子线程 is running
子线程 is over
主线程
"""

PS:在没有用join()方法时,主线程为什么要等着子线程结束才会结束整个进程?因为主线程结束也就标志着整个进程的结束,要确保子线程运行过程中所需的各项资源。

线程数据共享

在同一个进程内的多个线程数据是可以实现共享的。

from threading import Thread

money = 10000
def task():
global money
money = 1 t = Thread(target=task)
t.start()
t.join() # 确保线程执行了修改money的值
print(money) # 输出:1

线程对象属性和方法

os.getpid():查看当前所在进程。

active_count():获取当前存活线程,主线程也算。

current_thread().name:获取当前线程名称

from threading import Thread, current_thread, active_count
import time
import os def task():
time.sleep(1)
print('子线程名称:', current_thread().name)
print('子线程所在进程号:', os.getpid()) if __name__ == '__main__':
t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
t2.start()
print('当前存活线程数:', active_count()) # 获取当前存活线程,主线程也算
print('主线程名称:', current_thread().name)
print('主线程所在进程号:', os.getpid())

执行结果:

当前存活线程数: 3
主线程名称: MainThread
主线程所在进程号: 15048
子线程名称: Thread-1
子线程名称: Thread-2
子线程所在进程号: 15048
子线程所在进程号: 15048

守护线程

线程与进程一样,给对象的daemon属性值设为True即可。

from threading import Thread
import time def task():
print('子线程 is running')
time.sleep(3)
print('子线程 is over') if __name__ == '__main__':
t1 = Thread(target=task)
t1.daemon = True
t1.start()
print('主线程')

执行结果:

子线程 is running
主线程

但是在有多个线程的情况下,如果有一个子线程还在执行,那么这个守护线程就跟没有设置一样,因为主线程要等待所有非守护线程结束才可以结束。

GIL全局解释器锁

官方文档

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

解释:

GIL只存在于CPython(用C语言开发的)解释器中,不是python的特征。

GIL是加在CPython解释器上面的互斥锁,用于阻止同一个进程下的多个线程同时执行,原因是因为CPython解释器中的垃圾回收机制不是线程安全的,垃圾回收机制也算是一个线程,如果线程同时执行,那么可能会出现在一个线程中刚刚创建一个变量,垃圾回收机制线程就给回收了。

所以同一个进程下的多个线程要想执行必须先抢GIL锁,同一个进程下多个线程肯定不能同时运行。

拓展

如果多个任务都是IO密集型的,那么多线程更有优势,因为创建线程消耗的资源更少,并且因为有多个IO操作,线程会经常阻塞,一阻塞就会释放锁。

如果多个任务都是计算密集型,因为GIL的存在,多线程确实没有优势,但是可以用多进程。

IPC机制与线程的操作的更多相关文章

  1. 子进程回收资源两种方式,僵尸进程与孤儿进程,守护进程,进程间数据隔离,进程互斥锁,队列,IPC机制,线程,守护线程,线程池,回调函数add_done_callback,TCP服务端实现并发

    子进程回收资源两种方式 - 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源. - 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源. from multipr ...

  2. 4、网络并发编程--僵尸进程、孤儿进程、守护进程、互斥锁、消息队列、IPC机制、生产者消费者模型、线程理论与实操

    昨日内容回顾 操作系统发展史 1.穿孔卡片 CPU利用率极低 2.联机批处理系统 CPU效率有所提升 3.脱机批处理系统 CPU效率极大提升(现代计算机雏形) 多道技术(单核CPU) 串行:多个任务依 ...

  3. 消息队列,IPC机制(进程间通信),生产者消费者模型,线程及相关

    消息队列 创建 ''' Queue是模块multiprocessing中的一个类我们也可以这样导入from multiprocessing import Queue,创 建时queue = Queue ...

  4. 进程部分(IPC机制及生产者消费者模型)和线程部分

    进程部分 一:进程间通信IPC机制:由于进程之间的内存空间是相互隔离的,所以为了进程间的通信需要一个共享的内存空间, 但是共享带来的问题是数据在写的时候就不安全了,所以需要一种机制既有能共享的内存 空 ...

  5. Anciroid的IPC机制-Binder概述

    在Linux系统中,是以进程为单位分配和管理资源的.出于保护机制,一个进程不能直接访问另一个进程的资源,也就是说,进程之间互相封闭.但是,在一个复杂的应用系统中,通常会使用多个相关的进程来共同完成一项 ...

  6. Android之IPC机制

    Android IPC简介 任何一个操作系统都需要有相应的IPC机制,Linux上可以通过命名通道.共享内存.信号量等来进行进程间通信.Android系统不仅可以使用了Binder机制来实现IPC,还 ...

  7. IPC机制--Binder

    文章来自 Android技术内幕 系统卷 转:http://www.linuxidc.com/Linux/2011-08/40508.htm 什么是IPC机制以及IPC机制的种类 在Linux中,是以 ...

  8. IPC机制

    转:http://blog.chinaunix.net/uid-26125381-id-3206237.html  IPC 三种通信机制 2012-05-13 17:23:55 最近看了,IPC三种通 ...

  9. Android的IPC机制(一)——AIDL的使用

    综述 IPC(interprocess communication)是指进程间通信,也就是在两个进程间进行数据交互.不同的操作系统都有他们自己的一套IPC机制.例如在Linux操作系统中可以通过管道. ...

随机推荐

  1. led指示灯电路图大全(八款led指示灯电路设计原理图详解)

    led指示灯电路图大全(八款led指示灯电路设计原理图详解) led指示灯电路图(一) 图1所示电路中只有两个元件,R选用1/6--1/8W碳膜电阻或金属膜电阻,阻值在1--300K之间. Ne为氖泡 ...

  2. 学习webpack前的准备工作

    前言 由于vue和react的流行,webpack这个模块化打包工具也已经成为热门.作为前端工程师这个需要不断更新自己技术库的职业,真的需要潜下心来学习一下. 准备工作(针对mac用户) 安装 hom ...

  3. 面试题:给你个id,去拿到name,多叉树遍历

    前天面试遇到一个多叉树面试的题目,在这里分享记录一下. 题目:一个树形的数据(如下数据),面试官给你一个id,然后拿到对应的name? 数据结构大概是这个样子 var cityData = [ { i ...

  4. prometheus之查询语言

    PromQL(Prometheus Query Language)是 Prometheus 自己开发的表达式语言,语言表现力很丰富,内置函数也很多.使用它可以对时序数据进行筛选和聚合. 一.PromQ ...

  5. uniapp中生成二维码(附代码和插件)

    wxqrcode.js文件:  https://github.com/Clearlovesky/-js-jq-/tree/master/wxqrcode // 引入二维码库 import QR fro ...

  6. mybatisPlus crud操作注意事项

    1.调用IService里的update方法,如果是自定义根据除主键外其它字段更新的时候,如果给主键id设置其它值不会更新主键id,如果未设置主键id值或者设置为null,同样不会更新主键id. 2. ...

  7. Go xmas2020 学习笔记 05、Arrays, Slices, and Maps

    05-Arrays, Slices, and Maps. In memory. Array. Slice. fence post error. Compare Array and Slice . Ma ...

  8. 3.SRE.操作手册:基础篇

    SRE的根基起码应该包括:SLO.监控.告警.减少琐事和简单化. SLO(服务质量目标):用于描述服务可靠性的程度. SRE的职责并不只是将"所有工作"都自动化,并保持" ...

  9. 国产芯片DP9637-K总线收发器替代L9637D芯片和SI9241

    DP9637可以替代L9637D,低成本解决方案,只需要做简单硬件修改,感兴趣可以留言或者联系小编了解详细资料.   主要特性    电压工作范围 6V≤VBAT≤36V    具有超低休眠电流 ...

  10. JVM垃圾回收篇

    点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 基础概念 GC=jvm垃圾回收,垃圾回收机制是由垃圾回收器Garbage ...