Welcome!!!

F

伞兵一号,申请出战

并发编程(三)

并发编程(三)

消息队列

# 由于目前的知识储备还不够直接学习消息队列 所以先学习内置队列
"""
队列:先进先出(使用频率很高)
堆栈:先进后出(特定常见下用)
"""
# 以后我们会直接使用别人封装好的消息队列 实现各种数据传输
from multiprocessing import Queue

q = Queue(5)  # 括号里可以定义队列内部数据最大数量,不写默认2147483647
q.put(1)  # put方法给队列赋值
q.put(2)
q.put(3)
q.put(4)
q.put(5)
# q.put(6)  # 因为我设置的队列最大长度为5,赋值第六次会导致程序阻塞
data1 = q.get()
data2 = q.get()
data3 = q.get()
data4 = q.get()
data5 = q.get()
print(data1,data2,data3,data4,data5)  # 1 2 3 4 5
print(q.empty())  # 判断队列是否为空
print(q.full())  # 判断队列是否满了
print(q.get_nowait())  # 如果队列取不到值,直接报错

"""
    full()
    empty()
    get_nowait()
上述方法能否在并发的场景下精准使用???
    不能用!!!
    
之所以介绍队列是因为它可以支持进程间数据通信
如果在并发场景使用,会出现进程1赋值,进程2取值,极限情况下刚赋值就被取走,导致判断不准
"""

IPC机制(进程间通信)

"""
1.主进程与子进程数据交互
2.两个子进程数据交互
本质:不同内存空间中的进程数据交互
"""
from multiprocessing import Process,Queue

def func1(q):
    q.put('func1')

def func2(q):
    q.put('func2')

if __name__ == '__main__':
    q = Queue()  # 创建消息队列
    p1 = Process(target=func1,args=(q,))  # 进程1运行func1
    p2 = Process(target=func2,args=(q,))  # 进程2运行func2
    p1.start()
    p2.start()
    # 确保两个子进程全部运行结束
    p1.join()
    p2.join()
    q.put('主进程')
    while True:
        if q.empty():
            break
        print(q.get())
输出结果:
    func1
    func2
    主进程
'''
在做并发开发时,可以把所有进程公用的数据放进消息队列,供所有进程使用
'''

生产者消费者模型

# 生产者
	负责生产/制作数据
# 消费者
	负责消费/处理数据
"""
比如在爬虫领域中 
	会先通过代码爬取网页数据(爬取网页的代码就可以称之为是生产者)
	之后针对网页数据做筛选处理(处理网页的代码就可以称之为消费者)

如果使用进程来演示
	除了有至少两个进程之外 还需要一个媒介(消息队列)

以后遇到该模型需要考虑的问题其实就是供需平衡的问题
	生产力与消费力要均衡
"""
import random
import time
from multiprocessing import Process,Queue,JoinableQueue

# 生产者方法
def producer(q):
    for i in range(5):
        data = f'大厨制作了汉堡{i}'
        q.put(data)
        print(data)
        time.sleep(random.randint(1,3))


# 消费者方法
def consumer(q):
    while True:
        print(f'消费者吃了{q.get()}')
        q.task_done()  # 每取一次数据给队列一个反馈

if __name__ == '__main__':
    q = JoinableQueue()  # 产生队列
    p1 = Process(target=producer,args=(q,))
    p2 = Process(target=consumer,args=(q,))
    p1.daemon = True
    p2.daemon = True
    p1.start()
    p2.start()
    # 程序在生产者生产
    p1.join()
    p2.join()
    q.join()  # 消息队列全部结束
    print('主进程结束了')

线程理论

# 什么是线程
	进程:资源单位
  	线程:执行单位
 	进程占坑,线程操作(脑补)
  '''一个进程中至少有一个线程'''
  """
  进程仅仅是在内存中开辟一块空间(提供线程工作所需的资源)
  线程真正被CPU执行,线程需要的资源跟所在进程的要
  """
# 为什么要有线程
	开设线程的消耗远远小于进程
  	开进程
    	1.申请内存空间
      2.拷贝代码
   	开线程
    	一个进程内可以开设多个线程 无需申请内存空间、拷贝代码
      一个进程内的多个线程数据是共享的
  
"""
开发一个文本编辑器
	获取用户输入并实时展示到屏幕上
	并实时保存到硬盘中
多种功能应该开设多线程而不是多进程
"""

开设线程的两种方式

"""进程与线程的代码实操几乎是一样的"""
from threading import Thread

# 方式一
def my_print(name):
    print(f'我的名字是:{name}')

t = Thread(target=my_print,args=('petter',))
t.start()  # 我的名字是:petter
print('主线程结束')



# 方式二
class MyThread(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name
    def run(self):
        print(f'我的名字是:{self.name}')

t1 = MyThread('oscar')
t1.start()  # 我的名字是:oscar
print('主线程结束')

线程实现TCP服务端的并发

# 服务端
import socket
from threading import Thread

# 获取连接
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

# 封装通信方法
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()

# 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    client.send('hello world'.encode('utf8'))
    data = client.recv(1024)
    print(data.decode('utf8'))

线程join方法

from threading import Thread

def my_print(name):
    time.sleep(2)
    print(f'我的名字是:{name}')

t = Thread(target=my_print,args=('petter',))
t.start()  # 我的名字是:petter
t.join()
print('主线程结束')
"""
当我让子线程休眠两秒时,主线程会先输出结束
加上join后,主线程会等待子线程结束后再输出结束
主线程为什么要等着子线程结束才会结束整个进程  
    因为主线程结束也就标志着整个进程的结束 要确保子线程运行过程中所需的各项资源
"""

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

from threading import Thread

data = 1
def my_print():
    global data
    data = 2

t = Thread(target=my_print)
t.start()  # 我的名字是:petter
t.join()
print(data)  # 2
'''
只要这多个线程在同一个进程内,那么他们之间数据就是共享的
'''

线程对象属性和方法

1.验证一个进程下的多个线程是否真的处于一个进程
	主线程和子线程的进程号相同
2.统计进程下活跃的线程数
	active_count()  # 注意主线程也算!!!
3.获取线程的名字
	1.current_thread().name
  	MainThread  				 主线程
    Thread-1、Thread-2		子线程1,子线程2
  2.self.name(通过自定义类时使用)
eg:
    import os
    from threading import Thread, current_thread


    def my_print():
        print(os.getpid())  # 14224
        print(current_thread().name)  # Thread-1

    t = Thread(target=my_print)
    t.start()
    print(os.getpid())  # 14224
    print(current_thread().name)  # MainThread

守护线程

import time
from threading import Thread

def my_print(name):
    print('子线程开始')
    time.sleep(2)
    print('子线程结束')

t = Thread(target=my_print,args=('petter',))
t.daemon = True  # 要设置在start之前
t.start()
print('主线程结束')
输出结果:
    子线程开始
	主线程结束
    
'''由于我子线程休眠两秒,有没有使用join,所以主线程结束子线程立即结束,不会输出子线程结束语句'''

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.)
"""
1.回顾
	python解释器的类别有很多
		Cpython Jpython Ppython
	垃圾回收机制
		应用计数、标记清除、分代回收
    
GIL只存在于CPython解释器中,不是python的特征
GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行
原因是因为CPython解释器中的垃圾回收机制不是线程安全的

反向验证GIL的存在 如果不存在会产生垃圾回收机制与正常线程之间数据错乱
GIL是加在CPython解释器上面的互斥锁
同一个进程下的多个线程要想执行必须先抢GIL锁 所以同一个进程下多个线程肯定不能同时运行 即无法利用多核优势


强调:同一个进程下的多个线程不能同时执行即不能利用多核优势
	很多不懂python的程序员会喷python是垃圾 速度太慢 有多核都不能用

反怼:虽然用一个进程下的多个线程不能利用多核优势 但是还可以开设多进程!!!

再次强调:python的多线程就是垃圾!!!

反怼:要结合实际情况 
	如果多个任务都是IO密集型的 那么多线程更有优势(消耗的资源更少)
		多道技术:切换+保存状态
	如果多个任务都是计算密集型 那么多线程确实没有优势 但是可以用多进程
		CPU越多越好
	
以后用python就可以多进程下面开设多线程从而达到效率最大化
"""
1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势
2.GIL在实际编程中其实不用考虑

posted @   程序猿伞兵一号  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示