4.20python
进程补充及线程相关理论、方法概念
消息队列
from multiprocessing import Queue
q = Queue(5) # 定义一个队列并设置长度为5
1.往队列放数据
q.put(111) # 朝队列中存放数据
q.put(222)
q.put(333)
print(q.full()) # False 判断队列是否满了
q.put(444)
q.put(555)
print(q.full()) # True 队列已满
此时再往队列放数据就会超出最大长度,原地阻塞等待队列出现空位
2.从队列取数据
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_nowait()) # 队列里没有值直接报错
"""
full()
empty()
get_nowait()
上述方法能否在并发的场景下精准使用???
不能用!!!
之所以介绍队列是因为它可以支持进程间数据通信
"""
IPC机制(进程间通信)
1.主进程与子进程数据交互
2.两个子进程数据交互
本质:不同内存空间中的进程数据交互
from multiprocessing import Process, Queue
def producer(q):
q.put('666') # 往队列放数据
def consumer(q):
print(q.get()) # 取队列数据
if __name__ == '__main__':
q = Queue() # 设置队列
p1 = Process(target=producer, args=(q, ))
p2 = Process(target=consumer, args=(q,))
p1.start()
p2.start()
# q.put(123) # 主进程往队列中存放数据123
print('主进程')
# 结果:
主进程
666
#这样不同进程都在操作和使用这个队列,实现进程间通信
线程
1、什么是线程
进程:资源单位(在内存空间中申请一块内存)
线程:执行单位(在进程的内存中执行任务,资源从进程空间取)
eg:进程相当于车间,线程相当于车间里面的流水线
2、为什么要有线程?
答:开设线程的消耗远远小于进程
开进程:申请内存空间--->拷贝代码
开线程:无需申请内存、拷贝代码,进程内多个线程数据共享
总之,开多进程浪费内存空间及资源,线程解决了这个问题
开设线程的两种方法
#第一种
from threading import Thread
def test(name):
print(f'{name}')
name = '函数开设线程'
t = Thread(target=test, args=(name,))
t.start()
#第二种
from threading import Thread
class Mythread(Thread):
def __init__(self, username):
super().__init__()
self.username = username
def run(self):
print(f'{self.username}')
t = Mythread('类开设线程')
t.start()
线程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=('join方法', ))
t.start()
t.join() # 主线程代码等待子线程代码运行完毕之后再往下执行
print('主线程')
"""
主线程为什么要等着子线程结束才会结束整个进程
因为主线程结束也就标志着整个进程的结束 要确保子线程运行过程中所需的各项资源
"""
线程实现TCP服务端的并发
import socket
from throading import Throad
# 这里不用封装函数,因为使用线程做交互
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
def talk(sock):
data = sock.recv(1024) # 接收信息
print(data.decode('utf8'))
sock.send(data.upper()) # 发送信息
while True:
sock, addr = server.accept()
# 使用循环,每来一个客户端就创建一个线程做数据交互
t = Throad(target=talk, args=(sock,))
t.start()
同一进程内多个线程数据共享
from threading import Thread
money = 10000000000
def task():
global money
money = 1
t = Thread(target=task)
t.start()
t.join()
print(money) # 1
思考:线程更改进程内数据,数据也会被更改
线程对象属性和方法
#同一进程下多个线程的进程号一致
#统计进程下活跃的线程数
active_count()
#获取线程的名字
1.current_thread().name
MainThread # 主线程
Thread-1、Thread-2 # 子线程
2.self.name # 类对象获取线程名
守护线程
Thread类有一个名为deamon的属性,标志该线程是否为守护线程,默认值为False,当为设为True是表
示设置为守护线程。是否是守护线程有什么区别呢?
当deamon值为True,即设为守护线程后,只要主线程结束了,无论子线程代码是否结束,都得跟着结
束,这就是守护线程的特征。另外,修改deamon的值必须在线程start()方法调用之前,否则会报错。
from threading import Thread, current_thread, active_count, currentThread, enumerate
import time
def test():
time.sleep(3)
name = '守护'
print(f'{name}')
t = Thread(target=test, daemon=True)
# t.daemon = True
t.start()
t.join()
print(active_count())
print(current_thread().name)
GIL全局解释器锁
#GIL只存在于CPython解释器中,不是python的特征
GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行
原因是因为CPython解释器中的垃圾回收机制不是线程安全的
反向验证GIL的存在 如果不存在会产生垃圾回收机制与正常线程之间数据错乱
GIL是加在CPython解释器上面的互斥锁
#同一个进程下的多个线程要想执行必须先抢GIL锁 所以同一个进程下多个线程肯定不能同时运行 即无法利用多核优势
如果多个任务都是IO密集型的 那么多线程更有优势(消耗的资源更少)
多道技术:切换+保存状态
如果多个任务都是计算密集型 那么多线程确实没有优势 但是可以用多进程
CPU越多越好
#1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势
#2.GIL在实际编程中其实不用考虑
为什么有GIL
CPython 引进 GIL 其实主要就是这么两个原因:
#一是设计者为了规避类似于内存管理这样的复杂的竞争风险问题(race condition);
#二是因为 CPython 大量使用 C 语言库,但大部分 C 语言库都不是原生线程安全的(线程安全会降低性能和增加复杂度)。

浙公网安备 33010602011771号