学习python,从入门到放弃(35)
学习python,从入门到放弃(35)
消息队列
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() 等方法不可以在并发的场景下使用。
队列可以支持进程间数据通信。
IPC机制(进程间通信)
一个操作系统不同的进程,有自己的进程内存空间,其中的数据不共享,因此进程间的通信就需要采用一定的机制。
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('主进程')
生产者消费者模型
生产者负责生产/制作数据。
消费者负责消费/处理数据。
比如在爬虫领域中,会先通过代码爬取网页数据(爬取网页的代码就可以称之为是生产者),之后针对网页数据做筛选处理(处理网页的代码就可以称之为消费者)。
如果使用进程来演示,除了有至少两个进程之外,还需要一个媒介(消息队列)。
以后遇到该模型需要考虑的问题其实就是供需平衡的问题,生产力与消费力要均衡。
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() # 等待队列中数据全部被取出(一定要让生产者全部结束才能判断正确)
线程理论
进程是资源单位,而线程是执行单位。进程仅仅是在内存中开辟一块空间(提供线程工作所需的资源),线程真正被CPU执行,线程需要的资源跟所在进程的要,一个进程中至少有一个线程。
开设线程的消耗远远小于进程
开进程需要申请内存空间再拷贝代码。
开线程无需申请内存空间、拷贝代码,而且一个进程内可以开设多个线程,一个进程内的多个线程数据是共享的。
多种功能应该开设多线程而不是多进程
开设线程的两种方式
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=('jason',))
t.start() # 创建线程的开销极小 几乎是一瞬间就可以创建
print('主线程')
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('jasonNB')
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方法
因为主线程结束也就标志着整个进程的结束,要确保子线程运行过程中所需的各项资源,所以主线程要等着子线程结束才会结束整个进程。
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=('jason', ))
t.start()
t.join() # 主线程代码等待子线程代码运行完毕之后再往下执行
print('主线程')
同一个进程内的多个线程数据共享
from threading import Thread
money = 6666666
def task():
global money
money = 1
t = Thread(target=task)
t.start()
t.join()
print(money)
线程对象属性和方法
一个进程下的多个线程处于一个进程
active_count() 可以统计进程下活跃的线程数。
current_thread().name 和 self.name 可以获取线程的名字。
守护线程
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全局解释器锁
GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,而CPython是大部分环境下默认的Python执行环境。GIL 全称 gloabl interpreter lock (全局解释器锁) ,官方解释:
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.)
这主要是针对cpython解释器来说的,其他解释器不一样。
GIL遵循的原则:“一个线程运行 Python ,而其他 N 个睡眠或者等待 I/O.”(即保证同一时刻只有一个线程对共享资源进行存取)。
由于GIL涉及到底层实现,比较复杂,想要完全搞明白还是很困难的。但是只要记住2点:
-
在IO密集型型操作下,多线程还是可以的。比如在网络通信,time.sleep() 延时的时候。
-
在CPU密集型操作下,多线程性能反而不如单线程,此时只能用多进程。