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操作系统中可以通过管道. ...
随机推荐
- 前端规范(ES6BEMOOCSSSMACSS)
前端规范 在实际开发中,由于团队成员编码习惯不一,技术层次不同,开发前定制并遵循一种代码规范能提高代码质量,增加开发效率. Javascript Javascript规范直接参考airbnb: ES6 ...
- CSS揭秘之《背景图案》
网格 html { background: #58a; background-image: linear-gradient(white 2px, transparent 0), linear-grad ...
- java中异常到底有什么用?举例
异常的意义:马克-to-win:通过上面的例子,我们看出通过引入异常这种技术,即使出现不测(用户把0赋给除数),也可以让程序不崩溃,还能继续优雅 的运行.那,这种技术有用,值得学.马克-to-win: ...
- php 实验一 网页设计
实验目的: 1. 能够对整个页面进行html结构设计. 2. 掌握CSS+DIV的应用. 实验内容及要求: ***个人博客网页 参考Internet网上的博客网站,设计自己的个人网页,主要包括:图 ...
- 文档声明(Doctype)和<!Doctype html>有何作用? 严格模式与混杂模式如何区分?它们有何意义?
文档声明的作用: 文档声明是为了告诉浏览器,当前HTML文档使用什么版本的HTML来写的,这样浏览器才能按照声明的版本来正确的解析. <!doctype html> 的作用就是让浏览器进入 ...
- 修复tunl0-二进制安装calico
这篇博文很重要,出现这个问题导致pod之间无法通讯,pod无法连接外网. 出现的问题是二进制方式安装了节点之后, tunl0没有显示,通过ifconfig tunl0 up 启动tunl0 没有意义, ...
- JavaScript高级教程
JavaScript高级教程 基础总结深入 数据类型 分类 you are so nb! undefined :undefined string :任意字符串 sybmol: object:任意对象, ...
- 都2022年了,HDFS为何还如此能战!
摘要:HDFS也许不是最好的大数据存储技术,但依然是最重要的大数据存储技术. 本文分享自华为云社区<HDFS为何在大数据领域经久不衰?>,作者: JavaEdge. 1.概述 1.1 简介 ...
- npm 报错This is probably not a problem with npm. There is likely additional logging output above.
报错This is probably not a problem with npm. There is likely additional logging output above. 安装了一个插件后 ...
- 算法基础⑨搜索与图论--存在负权边的最短路--bellman_ford算法
bellman-ford算法 给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数. 请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 ...