IPC机制与线程的操作
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机制与线程的操作的更多相关文章
- 子进程回收资源两种方式,僵尸进程与孤儿进程,守护进程,进程间数据隔离,进程互斥锁,队列,IPC机制,线程,守护线程,线程池,回调函数add_done_callback,TCP服务端实现并发
子进程回收资源两种方式 - 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源. - 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源. from multipr ...
- 4、网络并发编程--僵尸进程、孤儿进程、守护进程、互斥锁、消息队列、IPC机制、生产者消费者模型、线程理论与实操
昨日内容回顾 操作系统发展史 1.穿孔卡片 CPU利用率极低 2.联机批处理系统 CPU效率有所提升 3.脱机批处理系统 CPU效率极大提升(现代计算机雏形) 多道技术(单核CPU) 串行:多个任务依 ...
- 消息队列,IPC机制(进程间通信),生产者消费者模型,线程及相关
消息队列 创建 ''' Queue是模块multiprocessing中的一个类我们也可以这样导入from multiprocessing import Queue,创 建时queue = Queue ...
- 进程部分(IPC机制及生产者消费者模型)和线程部分
进程部分 一:进程间通信IPC机制:由于进程之间的内存空间是相互隔离的,所以为了进程间的通信需要一个共享的内存空间, 但是共享带来的问题是数据在写的时候就不安全了,所以需要一种机制既有能共享的内存 空 ...
- Anciroid的IPC机制-Binder概述
在Linux系统中,是以进程为单位分配和管理资源的.出于保护机制,一个进程不能直接访问另一个进程的资源,也就是说,进程之间互相封闭.但是,在一个复杂的应用系统中,通常会使用多个相关的进程来共同完成一项 ...
- Android之IPC机制
Android IPC简介 任何一个操作系统都需要有相应的IPC机制,Linux上可以通过命名通道.共享内存.信号量等来进行进程间通信.Android系统不仅可以使用了Binder机制来实现IPC,还 ...
- IPC机制--Binder
文章来自 Android技术内幕 系统卷 转:http://www.linuxidc.com/Linux/2011-08/40508.htm 什么是IPC机制以及IPC机制的种类 在Linux中,是以 ...
- IPC机制
转:http://blog.chinaunix.net/uid-26125381-id-3206237.html IPC 三种通信机制 2012-05-13 17:23:55 最近看了,IPC三种通 ...
- Android的IPC机制(一)——AIDL的使用
综述 IPC(interprocess communication)是指进程间通信,也就是在两个进程间进行数据交互.不同的操作系统都有他们自己的一套IPC机制.例如在Linux操作系统中可以通过管道. ...
随机推荐
- ES6-11学习笔记--类与继承
ES5 中的类与继承: 类的定义: function People(name, age) { // this指向当前实例化对象 console.log(this); // 实例属性 this.name ...
- ubantu系统之 在桌面添加应用快捷方式
1. 首先在终端使用命令:sudo nautilus 这个命令会让你用root权限打开文件管理器,输入这个命令然后输入密码确认之后会弹出一个目录窗口;2. 然后我们就要找到目录:/usr/share/ ...
- Mybatis模糊查询结果为空的解决方案
写在前面 Mybatis使用模糊查询,查询结果为空的解决方案,我的代码是 select * from sp_user where 1=1 <if test="username!=nul ...
- Intellij IDEA中查看字节码
首先安装插件,这俩都勾上 Intellij IDEA 直接集成了一个工具菜单,可以直接查看字节码,打开 ByteCode 插件窗口方法如下:
- k8s,coredns内部测试node节点上的pod的calico是否正常的一个小技巧
最近由于master整个挂掉,导致相关一些基础服务瘫掉,修复中测试有些节点网络又出现不通的情况正常的启动相关一些服务后,测试一些节点,比较费劲,还有进入pod,以及还有可能涉及命名空间操作这里可以这样 ...
- c++中的类和对象_概念
类:事物所具有的共性(行为.属性)抽象出来封装在一起 对象:由类型实例化出对象 c++与c struct的区别:c中不能存放函数,只能存放属性,方法和属性分离,c++中则可存放函数. c中表示事物的方 ...
- 基于STM32单片机的简单红外循迹的实现
初步接触STM32,采用两路红外传感器实现小车循迹,稍显简略,如有不好的地方,欢迎大家指点改正
- STL空间分配器源码分析(一)
一.摘要 STL的空间分配器(allocator)定义于命名空间std内,主要为STL容器提供内存的分配和释放.对象的构造和析构的统一管理.空间分配器的实现细节,对于容器来说完全透明,容器不需关注内存 ...
- Python 连接Mysql数据库执行语句操作
学习Mysql模块的使用,模块命名的坑,解决SHA加密错误无法连接
- iOS全埋点解决方案-UITableView和UICollectionView点击事件
前言 在 $AppClick 事件采集中,还有两个比较特殊的控件: UITableView •UICollectionView 这两个控件的点击事件,一般指的是点击 UITableViewCell 和 ...