进程

进程间通信

IPC机制((Inter-Process Communication))

"""
1.主进程与子进程数据交互
2.两个子进程数据交互
本质:不同内存空间中的进程数据交互
"""
from multiprocessing import Process, Queue def producer(q):
# print('子进程producer从队列中取值>>>:', q.get())
q.put('子进程producer往队列中添加值') def consumer(q):
print('子进程consumer从队列中取值>>>:', q.get()) if __name__ == '__main__':
q = Queue()
p = Process(target=producer, args=(q, ))
p1 = Process(target=consumer, args=(q,))
p.start()
p1.start()
# q.put(123) # 主进程往队列中存放数据123
print('主进程')

队列(multiprocess.Queue)

概念

# 创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。 

# Queue([maxsize]) 创建共享的进程队列。
参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
底层队列使用管道和锁定实现。另外,还需要运行支持线程以便队列中的数据传输到底层管道中。

Queue的实例q具有以下方法:

# q.get( [ block [ ,timeout ] ] )
返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。 # q.get_nowait( )
同q.get(False)方法。 # q.put(item [, block [,timeout ] ] )
将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。 # q.qsize()
返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。 # q.empty()
如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。 # q.full()
如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。
# q.close()
关闭队列,防止队列中加入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如,如果某个使用者正被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。 # q.cancel_join_thread()
不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。 # q.join_thread()
连接队列的后台线程。此方法用于在调用q.close()方法后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。

消息队列

# 由于目前的知识储备还不够直接学习消息队列 所以先学习内置队列
"""
队列:先进先出(使用频率很高)
堆栈:先进后出(特定常见下用)
"""
# 以后我们会直接使用别人封装好的消息队列 实现各种数据传输
from multiprocessing import Queue q = Queue(5) # 自定义队列的长度
# 朝队列中存放数据
q.put(111)
q.put(222)
q.put(333)
print(q.full()) # False 判断队列是否满了
q.put(444)
q.put(555)
print(q.full()) # True
# q.put(666) # 超出最大长度 原地阻塞等待队列中出现空位
print(q.get())
print(q.get())
print(q.empty()) # False 判断队列是否空了
print(q.get())
print(q.get())
print(q.get())
print(q.empty()) # True
# print(q.get()) # 队列中没有值 继续获取则阻塞等待队列中给值
print(q.get_nowait()) # 队列中如果没有值 直接报错
"""
full()
empty()
get_nowait() 上述方法能否在并发的场景下精准使用???
不能用!!! 之所以介绍队列是因为它可以支持进程间数据通信
"""

生产者消费者模型

生产者消费者模型当中有两大类重要的角色,一个是生产者(负责造数据的任务),另一个是消费者 (接收造出来的数据进行进一步的操作)。

实现生产者消费者模型三要素:

1、生产者:负责生产/制作数据

2、消费者:负责消费/处理数据

3、队列(或其他的容哭器,但队列不用考虑锁的问题)

程序中出现明显的两类任务,一类任务是负责生产,另外一类任务是负责处理生产的数据的(如爬虫)时使用这个模型

"""
模型:生产者+媒介(队列)+消费者(用到的就是IPC机制)
这里队列用JoinableQueue这个模块,该模块有以下几个方法:
JoinableQueue()内有自带计数器,每当队列放一个数据的时候,会自动+1
task_done()方法,每从队列取出一个数据的时候,会自动减1
q.join(),当计数器为0时候才会执行
同时:将消费者进程设置成守护进程,这样q.join()执行完毕的时候,消费者子进程也会跟着结束
c1.daemon=True
"""
from multiprocessing import Process, Queue, JoinableQueue
import time
import random def producer(name, food, q):
for i in range(5):
data = f'{name}生产了{food}{i}'
print(data)
time.sleep(random.randint(1, 3)) # 模拟产生过程
q.put(data) def consumer(name, q):
while True:
food = q.get()
# if food == None:
# print('完蛋了 没得吃了 要饿死人了')
# break
time.sleep(random.random())
print(f'{name}吃了{food}')
q.task_done() # 每次去完数据必须给队列一个反馈 if __name__ == '__main__':
# q = Queue()
q = JoinableQueue()
# 生产者
p1 = Process(target=producer, args=('大厨jason', '韭菜炒蛋', q))
p2 = Process(target=producer, args=('老板kevin', '秘制小汉堡', q))
# 消费者
c1 = Process(target=consumer, args=('涛涛', q))
c2 = Process(target=consumer, args=('龙龙', q))
# 开启守护进程
c1.daemon = True
c2.daemon = True
# 开启进程
p1.start()
p2.start()
c1.start()
c2.start()
# 生产者生产完所有数据之后 往队列中添加结束的信号
# 保证生产全部生产完
p1.join()
p2.join()
# q.put(None) # 结束信号的个数要跟消费者个数一致才可以
# q.put(None)
"""队列中其实已经自己加了锁 所以多进程取值也不会冲突 并且取走了就没了"""
q.join() # 等待队列中数据全部被取出(一定要让生产者全部结束才能判断正确)
"""执行完上述的join方法表示消费者也已经消费完数据了"""

线程理论

线程概念

进程:资源单位
线程:执行单位
进程相当于车间(一个个空间),线程相当于车间里面的流水线(真正干活的)
'''一个进程中至少有一个线程'''
"""
进程仅仅是在内存中开辟一块空间(提供线程工作所需的资源)
线程真正被CPU执行,线程需要的资源跟所在进程的要
"""

线程存在的意义

或者说,为什么有了进程为什么还要有线程

进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?
其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。 现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让多个独立的过程,并行起来,这样很明显可以提高做事的效率。而实际的操作系统中,也同样引入了这种类似的机制————线程。 '''
开设线程的消耗远远小于进程
开进程
1.申请内存空间
2.拷贝代码
开线程
一个进程内可以开设多个线程 无需申请内存空间、拷贝代码
一个进程内的多个线程数据是共享的
'''

线程和进程的区别

线程与进程的区别可以归纳为以下4点:
1)地址空间和其它资源(如打开文件)∶进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信————需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程操作系统中,进程不是—个可执行的实体。

例如

开发一个文本编辑器
获取用户输入并实时展示到屏幕上
并实时保存到硬盘中
'''多种功能应该开设多线程而不是多进程'''

开设线程的两种方式

"""进程与线程的代码实操几乎是一样的"""
from threading import Thread
import time def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over') # 创建线程无需在__main__下面编写 但是为了统一 还是习惯在子代码中写
t = Thread(target=task, args=('jojo', ))
t.start() # 创建线程的开销极小 几乎是一瞬间就可以创建
print('主线程')

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('jojo桑')
t.start()
print('主线程')

线程实现TCP服务端的并发

# 仔细体会开设进程和线程的本质区别
import socket
from threading import Thread server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen() def talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper()) while True:
sock, addr = server.accept()
# 每类一个客户端就创建一个线程做数据交互
t = Thread(target=talk, args=(sock,))
t.start()

线程join方法

这里的join方法和进程中的用法一致。

join方法会让主线程等待子线程执行完后再去继续执行主线程后的代码。

from threading import Thread
import time def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over') t = Thread(target=task, args=('jojo', ))
t.start()
t.join() # 主线程代码等待子线程代码运行完毕之后再往下执行
print('主线程')
"""
主线程为什么要等着子线程结束才会结束整个进程
因为主线程结束也就标志着整个进程的结束 要确保子线程运行过程中所需的各项资源
"""

同一个进程内的多个线程数据共享

from threading import Thread

money = 10000000000
def task():
global money
money = 1 t = Thread(target=task)
t.start()
t.join()
print(money)

线程对象属性和方法

1.验证一个进程下的多个线程是否真的处于一个进程
验证确实如此
2.统计进程下活跃的线程数
active_count() # 注意主线程也算!!!
3.获取线程的名字
1.current_thread().name
MainThread 主线程
Thread-1、Thread-2 子线程
2.self.name
'''
threading.currentThread(): 返回当前的线程变量。 threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 threading.active_count: 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。# 注意主线程也算!!! threading.current_thread().name:获取当前线程的名字。
'''

守护线程

Thread类有一个名为deamon的属性,标志该线程是否为守护线程,默认值为False,当为设为True是表 示设置为守护线程。是否是守护线程有什么区别呢?

当deamon值为True,即设为守护线程后,只要主线程结束了,无论子线程代码是否结束,都得跟着结 束,这就是守护线程的特征。另外,修改deamon的值必须在线程start()方法调用之前,否则会报错。

from threading import Thread
import time def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over') t1 = Thread(target=task, args=('jason',))
t2 = Thread(target=task, args=('kevin',))
t1.daemon = True
t1.start()
t2.start()
print('主线程')

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

	GIL,是最流行的 Python 解释器 CPython 中的一个技术术语。它的意思是全局解释器锁,本质上是类似操作系统的 Mutex。每一个 Python 线程,在 CPython 解释器中执行时,都会先锁住自己的线程,阻止别的线程执行。
当然,CPython 会做一些小把戏,轮流执行 Python 线程。这样一来,用户看到的就是“伪并行”————Python 线程在交错执行,来模拟真正并行的线程。 # CPython 引进 GIL 其实主要就是这么两个原因:
一是设计者为了规避类似于内存管理这样的复杂的竞争风险问题(race condition);
二是因为 CPython 大量使用 C 语言库,但大部分 C 语言库都不是原生线程安全的(线程安全会降低性能和增加复杂度)。

所有的解释型语言都无法做到同一个进程下多个线程利用多核优势

"""
1.回顾
python解释器的类别有很多
Cpython Jpython Ppython
垃圾回收机制
应用计数、标记清除、分代回收 GIL只存在于CPython解释器中,不是python的特征
GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行
原因是因为CPython解释器中的垃圾回收机制不是线程安全的 反向验证GIL的存在 如果不存在会产生垃圾回收机制与正常线程之间数据错乱
GIL是加在CPython解释器上面的互斥锁
同一个进程下的多个线程要想执行必须先抢GIL锁 所以同一个进程下多个线程肯定不能同时运行 即无法利用多核优势 强调:同一个进程下的多个线程不能同时执行即不能利用多核优势
很多不懂python的程序员会喷python是垃圾 速度太慢 有多核都不能用 反怼:虽然用一个进程下的多个线程不能利用多核优势 但是还可以开设多进程!!! 再次强调:python的多线程就是垃圾!!! 反怼:要结合实际情况
如果多个任务都是IO密集型的 那么多线程更有优势(消耗的资源更少)
多道技术:切换+保存状态
如果多个任务都是计算密集型 那么多线程确实没有优势 但是可以用多进程
CPU越多越好 以后用python就可以多进程下面开设多线程从而达到效率最大化
""" GIL在实际编程中其实不用考虑

最新文章

  1. day 2远程连接Linux系统管理
  2. Python2
  3. servlet--页面自刷新
  4. oracle.jdbc.driver.OracleDriver和oracle.jdbc.OracleDriver这两个驱动的区别
  5. commons-logging 结合 log4j, 初始化生命周期 初探
  6. C# Web.config配置
  7. Unity 继承MonoBehaviour脚本 执行顺序 详解
  8. Math.pow();Math.sqrt();
  9. 【腾讯Bugly干货分享】舞动的表情包——浅析GIF格式图片的存储和压缩
  10. Sql server—— for xml path简单用法(可以按照分组把相同组的列中的不同的值,像字符串一样拼接在一起显示在分组之后的列中。)
  11. devexpress 之 ChartControl
  12. 转 C++函数返回值,你必须注意的问题
  13. HTTP请求模型和头信息参考
  14. [Algorithm] Circular buffer
  15. css3 伪元素和伪类选择器详解
  16. Openstack关于Regions和Availability Zones
  17. jQuery && jEasyUI 扩展功能集合
  18. java后台接受web前台传递的数组参数
  19. Spring源码解析-autowiring自动装配的实现
  20. 在Linux上使用Wine安装轻聊版的QQ的步骤讲解

热门文章

  1. python+pytest接口自动化(12)-自动化用例编写思路 (使用pytest编写一个测试脚本)
  2. vector存放边的方法
  3. Shell 脚本是什么?
  4. 什么是 Spring Profiles?
  5. REST 和RPC对比?
  6. 如何在 Mac 上强制退出 App
  7. 为什么要使用 rabbitmq?
  8. spring-boot-关于module自定义jar包打包无法给其他module使用
  9. Linux Yum仓库源配置
  10. 给定一个文件每一行是字符串,找出所有的逆序对,比如abc和cba是逆序的对