Java教程

2022.4.20进程补充及线程相关理论、方法概念

本文主要是介绍2022.4.20进程补充及线程相关理论、方法概念,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

2022.4.20进程补充及线程相关理论、方法概念

  • 消息队列
  • IPC机制(进程间通信)
  • 生产者消费者模型
  • 线程理论(重要)
  • 开设线程的两种方式
  • 线程实现TCP服务端并发
  • 线程join方法
  • 线程间数据共享
  • 线程对象属性及方法
  • 守护线程
  • GIL全局解释器锁

一、消息队列

ps:由于目前的知识储备还不够直接学习消息队列 所以先学习内置队列,以后我们会直接使用别人封装好的消息队列 实现各种数据传输

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、概念

生产者:负责生产/制作数据

消费者:负责消费/处理数据

"""
比如在爬虫领域中:
	会先通过代码爬取网页数据(爬取网页的代码就可以称之为是生产者)
	之后针对网页数据做筛选处理(处理网页的代码就可以称之为消费者)
"""
如果使用进程来演示
	除了有至少两个进程之外 还需要一个媒介(消息队列)
	以后遇到该模型需要考虑的问题其实就是供需平衡的问题
	即生产力与消费力要均衡

2、代码演示

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()
        time.sleep(random.random())
        print(f'{name}吃了{food}')
        q.task_done()  # JoinableQueue队列,每次去完数据必须给队列一个反馈
'''
问题:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步,进入阻塞态。

解决方式:
  普通方法:无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环
  JoinableQueue 方法: 生产者生产的每个数据上都做一个标记,消费者每 q.get() 取一个值,都用 q.task_done() 标记一次,q.join()感知队列中的数据全部处理完毕,再最终结束
'''        
if __name__ == '__main__':
    # q = Queue(),这里不使用普通队列
    q = JoinableQueue()  # 使用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.join()  # 等待队列中数据全部被取出(一定要让生产者全部结束才能判断正确)
    """执行完上述的join方法表示消费者也已经消费完数据了,相当于设置一道坎判断队列是否被取完,取完程序结束,守护进程直接结束,不会停留在阻塞态"""
    

四、线程理论

1、什么是线程

进程:资源单位(在内存空间中申请一块内存)

线程:执行单位(在进程的内存中执行任务,资源从进程空间取)

eg:进程相当于车间,线程相当于车间里面的流水线

一个进程至少有一个线程!

2、为什么要有线程?

答:开设线程的消耗远远小于进程

开进程:申请内存空间--->拷贝代码

开线程:无需申请内存、拷贝代码,进程内多个线程数据共享

总之,开多进程浪费内存空间及资源,线程解决了这个问题

五、开设线程的两种方法

其实开进程和线程的方法几乎是一样的,只不过关键字不同

代码演示:

from threading import Thread
import time

def task(name):
    print(f'{name}开始吃饭')
    time.sleep(3)
    print(f'{name}吃饱了')
# 创建线程无需在__main__下面编写 但是为了统一 还是习惯在子代码中写
t = Thread(target=task, args=('jason',))
t.start()
print('主线程')
# 结果(创建线程开销很小,几乎一瞬间就可以创建,因此运行很快):
jason开始吃饭  
主线程
Jason吃饱了

# 用类做线程对象
class MyThread(Thread):
    def __init__(self, username):
        self.username = username
        super().__init__()
     def run(self):
         print(f'{self.username}开始吃饭')
    	time.sleep(3)
    	print(f'{self.username}吃饱了')
t = MyThread('jason')
t.start()
print('主线程')
# 结果:
jason开始吃饭  
主线程
Jason吃饱了

六、线程实现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()

因此可以发现,使用线程实现并发更为好一点,因为效率高且节省资源

七、线程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=('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

九、线程对象属性和方法

1、同一进程下多个线程的进程号一致

2、如何统计进程下活跃的线程数

active_aount()

3、获取线程的名字

1.current_throad().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.start()
t2.start()
print('主线程')
# 结果:
jason is running
kevin is running
主线程
kevin is over
jason is over
# 注意,在这里,这个守护进程其实是起不了作用的,因为这里有两个子线程,其中一个是守护线程,但是主线程必须等待所有子线程运行结束才会真正结束,因为可能会有一些需要使用的数据,所以这里守护线程是起不了作用的,除非两个都是守护线程

十一、GIL全局解释器锁

回顾:

​ python解释器的类别有很多:
​ Cpython Jpython Ppython
​ 垃圾回收机制:
​ 应用计数、标记清除、分代回收

ps: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.)

解释:

GIL只存在于CPython解释器中,不是python的特征
GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行
原因是因为CPython解释器中的垃圾回收机制不是线程安全的

强调:

GIL是加在CPython解释器上面的互斥锁,
同一个进程下的多个线程要想执行必须先抢GIL锁,所以同一个进程下多个线程肯定不能同时运行,即:无法利用多核优势

优劣势:

优势:保证数据安全,不会被垃圾回收机制回收

劣势:无法发挥多核优势,但是可以通过开设多进程弥补

实际应用:

多任务为IO密集型:多线程更有优势,消耗资源更少(多道技术:切换+保存状态)

多任务为计算密集型:可以使用多进程,CPU越多越好

这篇关于2022.4.20进程补充及线程相关理论、方法概念的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!