26线程

发布时间 2023-09-21 11:36:12作者: 半糖+奶茶
消息队列
# 由于目前的知识储备还不够直接学习消息队列 所以先学习内置队列
"""
队列:先进先出(使用频率很高)
堆栈:先进后出(特定常见下用)
"""
# 以后我们会直接使用别人封装好的消息队列 实现各种数据传输
from multiprocessing import Queue


q = Queue(5)  # 自定义队列的长度  也可以里面不填,不填默认可以坐3万多人
# 朝队列中存放数据
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())  #111
print(q.get())  #222
print(q.empty())  # False  判断队列是否空了
print(q.get()) #333
print(q.get())  #444
print(q.get())  555
print(q.empty())  # True
# print(q.get())  # 队列中没有值 继续获取则阻塞等待队列中给值
print(q.get_nowait())  # 队列中如果没有值 直接报错
"""
    full()
    empty()
    get_nowait()
上述方法能否在并发的场景下精准使用???
    不能用!!!   并发的情况下,如果一个子进程拿数据,一个子进程放数据,是不准确的
    
之所以介绍队列是因为它可以支持进程间数据通信
"""

IPC机制(进程间通信)

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


def producer(q):
    # print('子进程producer从队列中取值>>>:', q.get())  #子进程producer从队列中取值>>>:123
    q.put('子进程producer往队列中添加值')

def consumer(q):
    print('子进程consumer从队列中取值>>>:', q.get())  #子进程consumer从队列中取值>>>:子进程producer往队列中添加值


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 # JoinableQueue可以判断队列中是否有值
import time
import random


def producer(name, food, q):  #生产者函数
    for i in range(5):  #生产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判断是否有食物了)


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()  #p1生产者开始生产
    p2.start()  #p2生产者开始生产
    c1.start()
    c2.start()
    # 生产者生产完所有数据之后 往队列中添加结束的信号
    p1.join()  #p1已经生产结束
    p2.join()  #p2已经生产结束
    # q.put(None)  # 结束信号的个数要跟消费者个数一致才可以
    # q.put(None)  #注意,如果用if判断食物是否是none,则消费者几个,none就要几个,因为感觉比较麻烦,所以我们可以不用if判断,用joinablequeue
    """队列中其实已经自己加了锁 所以多进程取值也不会冲突 并且取走了就没了"""
    q.join()  # 等待队列中数据全部被取出(一定要让生产者全部结束才能判断正确)
    """执行完上述的join方法表示消费者也已经消费完数据了"""

 线程理

# 什么是线程
  进程:资源单位
  线程:执行单位
    进程相当于车间(一个个空间),线程相当于车间里面的流水线(真正干活的)
  '''一个进程中至少有一个线程'''


# 为什么要有线程
    开设线程的消耗远远小于进程
    开进程
        1.申请内存空间
        2.拷贝代码
    开线程
        1.一个进程内可以开设多个线程 无需申请内存空间、拷贝代码
        2.一个进程内的多个线程数据是共享的
  
"""
开发一个文本编辑器
    获取用户输入并实时展示到屏幕上
    并实时保存到硬盘中
多种功能应该开设多线程而不是多进程
"""

开设线程的两种方式

"""进程与线程的代码实操几乎是一样的"""
1.通过对象

from threading import ThreadThread 表示线程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('主线程')
最后打印结果是name..running,主线程, name..over(子进程可以立马启动)
如果是用进程结果 主进程, name..running name..over 2通过类
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 = 10000000000
def task():
    global money
    money = 1

t = Thread(target=task)
t.start()
t.join()  #确保子线程结束后主线程才结束
print(money)  #1(主子线程在同一空间,数据共享)


但是在进程中,子进程的money和主进程的money属于两个空间,所以打印出来的money是10000000000

线程对象属性和方法

1.验证一个进程下的多个线程在同一空间,属于同一线程
2.统计进程下活跃的线程数
    active_count()  # 注意主线程也算!!!
3.获取线程的名字
    1.current_thread().name
      MainThread                   主线程
        Thread-1、Thread-2        子线程
    2.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的子线程设置为守护线程,主线程结束,即结束
t1.start()
t2.start() #t2的子线程不是守护线程
print('主线程')

'''最后的结果是     jason is running ,       kevin is running  , 主线程
                         jason is over  ,       kevin is over'''

#可以知道当线程里面有一个子线程存在没有结束时候,那么其他线程就算是守护线程,也不会因为主线程完成后就结束,相当于此时的守护线程是无效的

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在实际编程中其实不用考虑

ps:明天代码验证多进程和多线程都有用!!!