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 语言库都不是原生线程安全的(线程安全会降低性能和增加复杂度)。
posted @ 2022-05-20 15:12  槐序八  阅读(39)  评论(0)    收藏  举报