路飞学城Python-Day31

19-生产者消费者模型
生产者:生成数据的任务
消费者:处理数据的任务
在并发编程的过程中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理,才能继续生产数据;同样的,如果消费者处理数据的能力大于生产者,那么消费者就必须等待生产者
什么是生产者和消费者
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题,生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者。
这个阻塞队列就是用来给生产者和消费者的
生产数据的目的就是给消费者去处理
from multiprocessing import Process,Queue
import time
# 生产者在消费者消费的同时不能生产,消费者在生产的同时不能消费
# 这样的问题会导致生产者和消费者之间彼此在互相等待
# 生产者消费者模型就是可以解决这样的问题,
# 在生产者和消费者之间建立一个容器,生产者往容器里生产,消费者去容器里取,这样就解决了耦合问题
# 好处1:解耦合
# 好处2:平衡了数据处理的时间差
# 基于队列实现生产者消费者模型
 
def producer(q):
    for i in range(10):
        res = '制造品%s' % i
        time.sleep(0.5)
        print('生产者生产了%s' % res)
        q.put(res)
 
 
def consumer(q):
    while True:
        res = q.get()
        if res is None: break
        time.sleep(1)
        print('消费者消费了%s'% res)
 
if __name__ == '__main__':
    # 启动队列->容器
    q = Queue()
#     生产者
    p = Process(target=producer, args=(q, ))
 
#   消费者
    c = Process(target=consumer, args=(q, ))
 
    p.start()
    c.start()
    p.join()
    # 确保所有的生产者都生产完毕后才会给消费者发送结束信号(有几个消费者就要发几个结束信号)
    q.put(None)
    print('主进程')
 
 
【总结】
程序中有两类角色
一类负责生产数据(生产者)
一类负责处理数据(消费者)
生产者和消费者解决的问题:
1.平衡生产者消费者的数据处理速度差
2.程序解耦合
注意:实际使用过程中并不会使用Queue的方式实现生产者和消费者模型,而是使用Rabbitmq(基于网络实现)

20-JoinableQueue的使用
from multiprocessing import Process,JoinableQueue
import time
# 生产者在消费者消费的同时不能生产,消费者在生产的同时不能消费
# 这样的问题会导致生产者和消费者之间彼此在互相等待
# 生产者消费者模型就是可以解决这样的问题,
# 在生产者和消费者之间建立一个容器,生产者往容器里生产,消费者去容器里取,这样就解决了耦合问题
# 好处1:解耦合
# 好处2:平衡了数据处理的时间差
# 基于队列实现生产者消费者模型
# 回顾守护进程
def producer(q):
    for i in range(2):
        res = '制造品%s' % i
        time.sleep(0.5)
        print('生产者生产了%s' % res)
        q.put(res)
    q.join()
 
 
def consumer(q):
    while True:
        res = q.get()
        if res is None: break
        time.sleep(1)
        print('消费者消费了%s'% res)
        q.task_done()
 
if __name__ == '__main__':
    # 启动队列->容器,JoinableQueue可以执行Queue.join
    q = JoinableQueue()
#     生产者
    p = Process(target=producer, args=(q, ))
 
#   消费者
    c = Process(target=consumer, args=(q, ))
    c.daemon = True
    p.start()
    c.start()
    p.join()
    print('主进程')
 

21-什么是线程
线程的概念:
操作系统比作公司,那么操作系统每启动一个进程就相当于成立一个部门,
为什么需要多个进程?
不同的进程有不同的作用,数据之间是应该独立隔离处理,进程实际是一个资源单位
进程需要正常运行就必须需要至少一个线程,多个线程在一个进程内是共享进程内的数据的,进程之间是不共享数据的
开进程的开销大,因为进程是需要申请内存资源的,操作系统内就要分配,开线程只需要在已经分配好的内存中再去申请执行就可以了
为什么要用多线程?
并发进程可以处理,但是进程间是不共享数据的,这就需要对数据进行不断的互相传输,那采用线程的方案是更合理的

22-开启线程的两种方式
方式一
import time
import random
from threading import Thread
 
 
def piao(name):
print('%s piaoing' % name)
time.sleep(random.randrange(1,5))
 
if __name__ == '__main__':
t1 = Thread(target=piao, args=('panda',))
t1.start()
# 站在执行的角度,这个就是主线程,站在资源的角度就是主进程
print('主线程')
 
方式二
import time
import random
from threading import Thread
 
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
 
 
def run(self):
print('%s is running' % self.name)
time.sleep(random.randrange(1,5))
print('%s is ending' % self.name)
 
if __name__ == '__main__':
t1 = MyThread('panda')
t1.start()
print('主线程')

23-进程与线程的区别
1.开进程的开销远大于开线程的开销
进程在启动之后由于要操作系统给进程分配内存空间,所以执行的时间上没有线程那样快速
import time
from threading import Thread
 
 
def piao(name):
print('%s piaoing' % name)
time.sleep(2)
 
if __name__ == '__main__':
t1 = Thread(target=piao, args=('panda',))
t1.start()
# 站在执行的角度,这个就是主线程,站在资源的角度就是主进程
print('主线程')
2.同一个进程内的多个线程共享该进程的地址内存空间
from threading import Thread
from multiprocessing import Process
n = 100
def task():
global n
n = 0
 
 
if __name__ == '__main__':
# p1 = Process(target=task, )
# p1.start()
# p1.join()
t1 = Thread(target=task, )
t1.start()
t1.join()
print('主线程', n)
 
3.pid->进程的id号
每开一个进程就会给进程分配id号
from threading import Thread
from multiprocessing import Process,current_process
import os
def task():
print('线程%s' % os.getpid())
 
 
if __name__ == '__main__':
# p1 = Process(target=task, )
# p1.start()
# p1.join()
t1 = Thread(target=task, )
t1.start()
t1.join()
print('主', os.getpid())

24-Thread对象的其他属性或方法
1.设置线程名称
setName
2.查看线程是否存活
is_alive
3.查看当前执行线程活跃数
active_count
4.显示列表格式的,把当前活跃的线程对象放入列表里
enumerate
from threading import Thread,currentThread,active_count,enumerate
import time
def task():
print('%s is running' %currentThread().getName())
time.sleep(2)
print('%s is done' % currentThread().getName())
 
 
if __name__ == '__main__':
t = Thread(target=task, name='子线程-1')
t.start()
# 设置线程名称
# t.setName('子线程---1')
# t.join()
# 查看线程是否存活
# print(t.is_alive())
# print('主线程的名称',currentThread().getName())
# t.join()
# 查看当前执行线程活跃数
# print(active_count())
# 显示列表格式的,把当前活跃的线程对象放入列表里
print(enumerate())

25-守护线程
一个进程应该在什么时候被回收?
开进程的目的是为了执行一个命令,什么时候进程结束什么时候进程就应该结束,在进程内如果只有一个主线程的话,这个进程就应该被回收,一个进程可以开多个线程,当多个线程都结束任务的时候,那么进程就应该结束
主线程代码运行完以后,还是需要等其他的线程结束以后才能结束
【总结】
在一个进程内(只有一个线程),如果不开线程,默认就一个主线程,主线程代码运行完毕,进程就可以结束了
在一个进程内(多个线程),主线程在代码运行完以后,仍然要等其他的线程把代码运行完以后才能结束
主线程从运行角度上就代表了进程的运行周期,也就是说代码运行完毕以后要等非守护线程的其他线程都允许完毕以后才能结束,守护线程等到所有进程内所有非守护线程都结束了以后才能结束。
# from threading import  Thread
# import time
# def sayhi(name):
#     time.sleep(2)
#     print('%s say hello' % name)
#
# if __name__ == '__main__':
#     t = Thread(target=sayhi, args=('panda',))
#     t.setDaemon(True)
#     t.start()
#     print('主线程')
#     print(t.is_alive())
# ----------------------------------------------------------------------
from threading import Thread
import time
 
 
def foo():
    print('start-123')
    time.sleep(1)
    print('end-123')
 
 
def bar():
    print('start-456')
    time.sleep(3)
    print('end-456')
 
 
if __name__ == '__main__':
    t1 = Thread(target=foo)
    t2 = Thread(target=bar)
    t1.daemon = True
    t1.start()
    t2.start()
    print('main-----------')
 

26-互斥锁
互斥锁:mutex
把并行变成串行,牺牲了执行的效率来保证数据的安全
一个进程内开启了多个线程,共享这一个进程的地址的内存空间
这意味着不需要一个进程内的多个线程是能实现数据共享的,数据共享带来的问题就是竞争,那就需要加锁
这里如果不加锁的话,所有的线程都会拿到数据之后都可以接着执行,但是这样的话数据就没法保证是安全的
#mutex
from threading import Thread,Lock
import time
n = 100
def task():
    global n
    mutex.acquire()
    temp = n
    time.sleep(0.1)
    n = temp-1
    mutex.release()
 
if __name__ == '__main__':
    mutex = Lock()
    t_l = []
    for i in range(100):
        t = Thread(target=task)
        t_l.append(t)
        t.start()
    for t in t_l:
        t.join()
    print('主',n)
 
 

27-GIL的基本概念
互斥锁的概念就是将并行变成串行,牺牲效率保证了数据的安全,针对不同的数据的保护就要加不同的锁
互斥锁的精髓在于保证了局部数据是安全的
GIL就是解释器级别的一把锁,本质上就是一把互斥锁
运行一个py文件,一共产生了几步?
1.先产生一个进程,为进程分配内存空间
2.将python代码作为参数传给解释器
3.解释器收到python的参数后执行
GIL全局解释锁的作用:
1.保证了线程数据的执行安全,但是牺牲了数据的处理效率
2.对于cpython解释器来说,并不能用上多核优势(当启动了一个进程,这个进程内有多个线程,这样是不能同时执行多个线程的,只能单线程的执行)
垃圾回收线程不是一直存在的,是cpython解释器定期去销毁的

28-GIL与自定义互斥锁的区别
python有GIL全局解释锁的存在,保证了python解释器能够在一个进程内的多个线程,让线程单个单个的执行
为什么还要加互斥锁来保证数据安全呢?
GIL是解释器级别的,是保证垃圾回收机制的数据的,代码内的互斥锁是保证了代码之间的数据的安全,不同的数据级别要用不同的锁
底层原理:
分析:
1、100个线程去抢GIL锁,即抢执行权限
2、肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
3、极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
4、直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程
【总结】
GIL保证了一个进程内的多个线程只能有一个线程执行,目的是为了保证垃圾回收是安全的
针对不同的数据要加不同的锁,解释器级别的锁是保护解释器级别的锁,所有的线程实际上是先拿GIL锁
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。>有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

29-GIL与多线程
有了GIL的存在,同一个时刻有了多个线程,那也只能有一个线程执行,如果想用多核优势怎么办?
CPU是做计算的,多个CPU提升的是计算性能,对于纯计算任务来说,CPU越多,计算的执行并发越多,执行时间就是运算时间最长的那个,如果4个CPU是纯I/O的任务,再多的CPU也没有时间上的节省
结论:
1、对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用
2、当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高)
,这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,
从而进一步分析python的多线程到底有无用武之地
如果并发的多个任务是计算密集型:多进程效率高
如果并发的多个任务是I/O密集型:多线程效率高
计算密集型用多进程
from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res = 0
    for i in range(10000000):
        res *= i
if __name__ == '__main__':
    l = []
    print(os.cpu_count())
    start = time.time()
    for i in range(4):
        # p = Process(target=work)
        p = Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('spend time %s'%(stop-start))
IO密集型用多线程
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    print('===>')
 
if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
        # p=Thread(target=work) #耗时2s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

30-死锁与递归锁
什么是死锁?
from threading import Thread,Lock
import time
mutexA = Lock()
mutexB = Lock()
 
class MyThread(Thread):
    def run(self):
        self.f1()
        self.f2()
    def f1(self):
        mutexA.acquire()
        print('%s 拿到了A'% self.name)
        mutexB.acquire()
        print('%s 拿到了B' % self.name)
        mutexB.release()
        mutexA.release()
    def f2(self):
        mutexB.acquire()
        print('%s 拿到了B' % self.name)
        time.sleep(0.1)
        mutexA.acquire()
        print('%s 拿到了A'%self.name)
        mutexA.release()
        mutexB.release()
 
 
if __name__ == '__main__':
    for i in range(10):
        t = MyThread()
        t.start()
什么是递归锁?
# 递归锁:可以连续acquire多次,每一次acquire一次,计数器就+1,只要计数为0,才能被其他线程抢到
from threading import Thread,RLock
import time
mutexB = mutexA = RLock()
 
class MyThread(Thread):
    def run(self):
        self.f1()
        self.f2()
    def f1(self):
        mutexA.acquire()
        print('%s 拿到了A'% self.name)
        mutexB.acquire()
        print('%s 拿到了B' % self.name)
        mutexB.release()
        mutexA.release()
    def f2(self):
        mutexB.acquire()
        print('%s 拿到了B' % self.name)
        time.sleep(0.1)
        mutexA.acquire()
        print('%s 拿到了A'%self.name)
        mutexA.release()
        mutexB.release()
 
 
if __name__ == '__main__':
    for i in range(10):
        t = MyThread()
        t.start()

31-信号量

32-Event事件

33-定时器

34-线程queue

35-多线程实现并发的套接字通信

36-进程池线程池

37-异步调用与回调机制

38-进程池线程池小练习

39-协程介绍

40-协程实现与总结

41-greenlet模块

42-gevent模块

43-gevent异步提交任务

44-基于gevent模块实现并发的套接字通信

45-IO模型介绍

46-阻塞IO模型

47-非阻塞IO模型

48-多路复用IO模型

49-异步IO模型

 
 
 
 
 

猜你喜欢

转载自www.cnblogs.com/pandaboy1123/p/9388664.html