05. 生产者消费者模式、线程

一、生产者消费者模式

1、理论:
	-生产者模式是通过一个容器来解决(类程序)生产者和消费者的强耦合问题。
    -生产者和消费者之间并不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产数据后不用等待消费者处理,直接扔进阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里面取
    -阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的工作能力,来提高程度的整体处理数据的速度。
2、实现方式:
	生产者<——>队列<——>消费者
3、代码实现
# 初级模型:
from multiprocessing import Process,Queue
import time, random

def producer(name,food,q):
    for i in range(10):
        data = f"{name}生产了{food}"
        time.sleep(random.randint(1,3))
        print(data)
        q.put(food)
def consumer(name,q):
    while True:
        food = q.get()
        # 判断一下food是否是None,如果是就结束进程,避免一直等待
        if food is None: break 
        time.sleep(random.randint(1,3))
        print(f'{name}消费了{food}')
if __name__ == '__main__':
    q = Queue()
    p = Process(target=producer, args=('jason','饺子',q))
    p.start()
	c = Process(target=consumer, args=('jack',q))
    c.start()
    # 为避免消费者因为q里面没有数据而卡在q.get()上面,我们加一个q.put(None)
    p.join()
    q.put(None)  # 发送结束信号
4、JoinableQueue([maxsize]):
    就像是一个Queue对象,但是队列允许项目的使用者通知生产者项目已经被成功处理,通知进程是使用共享的信号和条件变量是实现的。
    方法介绍:
    JoinableQueue的实例q除了和Queue对象相同的方法之外还具有:
    q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理,如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常,
    q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。
        阻塞将持续到队列中的每一个项目均调用q.task_done()方法为止
-代码实现:
from multiprocesssing import Process,JoinableQueue
import time,random

def producer(name,food,q):
	for i in range(10):
		time.sleep(random.randint(1,3)
		res = f"{food}{i}"
		print( f"{name}生产了{food}")
		q.put(res)
	q.join()
	
def consumer(name,q):
    while True:
        food = q.get()
        time.sleep(random.randint(1,3))
        print(f'{name}消费了{food}')
		q.task_done()
		
if __name__ == '__main__':
    q = JoinableQueue()
    p1 = Process(target=producer, args=('jason','饺子',q))
    p2 = Process(target=producer, args=('cc','蛋糕',q))
    p3 = Process(target=producer, args=('小芳','面包',q))
   
	c1 = Process(target=consumer, args=('jack',q))
	c2 = Process(target=consumer, args=('xx',q))
	c1.daemon = True
	c2.daemon = True
    
    pp = [p1,p2,p3,c1,c2]
    for i in pp:
    	i.start()
    # 为避免消费者因为q里面没有数据而卡在q.get()上面,我们加一个q.put(None)
    p1.join()
    p2.join()
    p3.join()
    print('主')
    # 主进程等-->p1,p2,p3等-->c1,c2
    # p1,p2,p3结束了,证明c1,c2肯定也全部收完了,也就没有继续运行的必要了,所以设置成守护进程

5、总结JoinableQueue()
	每放一个值,数字加一
	取值不会减一,q.task_done()
	q.join() 一直阻塞,当q没有值了,才继续运行

二、线程理论

计算机相当于是医德大工厂,工厂里面有一个个的车间(进程),有很多人(线程)干不同的事情
真正干活的是线程--》线程是CPU调度的最小单位
每一个进程中至少要有一个线程--》进程是资源分配的最小单位
相比进程,线程开销更小,更轻量级

三、开启线程的两种方式

  • 第一种
from threading import Thread
import time

def task():
    print('开始')
    time.sleep(1)
    print('结束')
    
if __name__ == '__main__':
    t = Thread(target=task, )
    t.start()
    print('主')
  • 第二种
from threading import Tread
import time

class MyThread(Thread):
    def run(self):
        print('开始')
        time.sleep(1)
        print('结束')
        
if __name__ == '__main__':
    t = MyThread()
    t.start()
    print('主')

四、线程对象的join方法

作用:等待子线程执行结束

from threading import Thread
import time

def task(n):
    print('开始')
    time.sleep(n)
    print('结束')
    
if __name__ == '__main__':
    t = Thread(target=task, args=(2,) )
    t.start()
    t.join()  # 等子线程t执行完成,主线程才能继续
    
    print('主')

五、同一线程下的多个线程数据共享

在线程内修改全局变量是可以在全局查找到的

from threading import Thread
import time

x = 10

def task(n):
    global x
    x = n
    print('开始')
    time.sleep(n)
    print('结束')

if __name__ == '__main__':
    t1 = Thread(target=task, args=(15,))
    t1.start()
    
    t2 = Thread(target=task, args=(20,))
    t2.start()
    
    t1.join()
    t2.join()
    print(x)  # 20
    print('主')

六、线程对象及其他方法

-Thread实例对象的方法
	t.isAlive():返回线程是否还在运行(不推荐使用),推荐使用t.is_alive()
    t.getName():返回线程名,同t.name
    t.setName():设置线程名
    t.ident  :可以当作是线程的id号(线程是没有id号的,此方法主要用于区分不同线程)

-threading模块提供的方法
    current_count():返回当前正在运行的线程数量

-代码演示:
from threading import Thread, current_thread,active_count
import time
import os


def task(n):
    print('开始')
    print(current_thread().name)   # 打印线程名字
    print(os.getpid())  # 打印进程id
    time.sleep(n)
    print('结束')



if __name__ == '__main__':
    t1 = Thread(target=task,name='egon',args=(2,))  # 可以给线程命名
    t2 = Thread(target=task,args=(8,))  # 不命名就默认命名为thread—1、thread-2...
    t1.start()
    t2.start()
    t1.join()
    print('---------',t1.is_alive())  # False
    print('---------',t2.is_alive())  # True

    # 当作线程id号
    print('*********',t1.ident)
    print('*********',t2.ident)

    print(os.getpid())
    print(active_count())  # 打印出2 ,开了两个线程,其中一个运行完毕,还有一个主线程

七、守护线程

无论是进程还是线程,都遵循守护进程/线程 会等待主进程/线程运行完毕后被销毁

**强调:**运行完毕不代表终止运行

# 对于主进程来说,运行完毕指的是主进程代码运行完毕
# 对于主线程来说,运行完毕指的是主线程所在的进程内,所有的非守护线程统统运行完毕,主线程才算运行完毕

代码演示:

from threading import Thread, current_thread, active_count
import time


def task(n):
    print(f'{current_thread().name}开始')
    time.sleep(n)
    print(active_count())
    print(f'{current_thread().name}结束')


if __name__ == '__main__':
    t1 = Thread(target=task, name='fine', args=(10,))
    t1.daemon = True
    t1.start()

    t2 = Thread(target=task, args=(2,))
    t2.start()
    print('主')


'''
# 运行结果:
fine开始
Thread-1开始主

3
Thread-1结束
'''

八、线程互斥锁

注意点:

1、线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,
	但是如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立即交出来。
2、join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,
	要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

代码演示:

from threading import Thread, Lock
import time,random

x = 100

def task(mutex):
    global x
    # 在修改数据的时候加锁
    mutex.acquire()
    temp = x
    time.sleep(0.1)
    x = temp - 1
    # 修改完以后释放锁,以便其他线程能再次抢到锁
    mutex.release()
    
if __name__ == '__main__':
    ll = []
    mutex = Lock()
    for i in range(10):
        t = Thread(target=task,args=(mutex,)
        t.start()
        ll.append(t)
    for i in ll:
    	i.join()
    print(x)  # 89              

九、GIL全局解释器锁理论

#1 python的解释器有很多,cpython,jpython,pypy(python写的解释器)
#2 python的库多,库都是基于cpython写起来的,其他解释器没有那么多的库
#3 cpython中有一个全局大锁,每条线程要执行,必须获取到这个锁
#4  为什么会有这个锁呢?python的垃圾回收机制可能会将刚定义好还没有赋值的变量名直接回收掉
#5 python的多线程其实就是单线程
#6 某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行

# 7 总结:cpython解释器中有一个全局锁(GIL),线程必须获取到GIL才能执行,我们开的多线程,不管有几个cpu,同一时刻,只有一个线程在执行(python的多线程,不能利用多核优势)

# 8 如果是io密集型操作:开多线程
# 9如果是计算密集型:开多进程
以上两句话,只针对与cpython解释器
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值