并发编程 (已更完)

01、理论

操作系统发展史

多道技术

单核实现并发的效果

  • 并行:真正意义上的同时执行

  • 并发:看上去像同时运行的

ps:

  • 并行属于并发
  • 单核计算机肯定不能实现并行,但是可以实现并发

时间上的复用与空间上的复用

  • 空间上的复用:
    • 多个程序共用一套计算机
  • 时间上的复用:
    • 洗衣服30s 、做饭30s、烧水40s

    • 单道需要110s,躲到只需要任务时长最长的哪一个

    • ps :边吃饭,边打游戏,

    • 切换+保存状态

"""
切换(CPU)分为两种情况
	1. 当一个程序遇到IO操作的时候,操作系统会剥夺该程序的CPU执行权限
	   作用:提高了CPU的利用率,并不影响程序的执行效率
	   
    2. 当一个程序长时间占用CPU的时候,操作系统也会剥夺该程序的CPU执行权限
       作用:降低了程序的执行效率,(原本的时间+切换的时间)
     
    
"""

02、进程

  • 程序与进程的区别
"""
程序就是一堆躺在硬盘上的代码,是“死”的
进程则表示程序正在执行的过程,是“活”的
"""

进程的调度

  • 先来先服务调度算法

    """对长作业有利,对短作业无益"""
    
  • 短作业优先调度算法

    """对短作业有利。对长作业无益"""
    
  • 时间片轮转法+多级反馈队列

    """
    时间片:将固定的时间分为N多份,每一份就表示一个时间片
    """
    
进程运行的三状态图

在这里插入图片描述

  • 就绪态:一切程序必须先经过就绪态,才能加入运行态
  • 运行态:正在被CPU执行
  • 阻塞态:程序遇到IO操作
同步和异步
"""描述的是任务的提交方式"""
  • 同步:任务提交之后,原地等待任务的返回结果,等待的过程中不做任何事(干等) “ 程序层面上表现出来的是卡住了”

  • 异步:任务提交后,不原地等待任务的返回结果,直接去做其他事情

    ”任务的返回结果会有一个异步回调机制自动处理“

阻塞和非阻塞
"""描述的是程序的运行状态"""
  • 阻塞:阻塞态

  • 非阻塞:就绪态,运行态

理想状态:我们应该让我们写的代码永远处于就绪态和运行态之间切换

上述概念中最高效的一种组合是异步非阻塞

开启进程的两种方式

代码开启进程和线程的方式,代码书写基本是一致的

  • 第一种
from multiprocessing import Process
import time

def task(name):
    print('{} is running'.format(name))
    time.sleep(3)
    print('%s is over' % name)
    
if __name__ == '__main__':
    #1.创建一个进程对象
    p=Process(target=task,args=('zhao',))
    #容器类型哪怕里面只有1个元素,建议一定要用逗号隔开
    #2.开启进程
    p.start()  #告诉操作系统帮你创建一个进程  ,异步
    print('主进程')
    
    
#执行结果如下:
"""
主进程
zhao is running
zhao is over
"""
   
  • windows 操作系统下,一定要再main下创建进程

  • 因为windows下创建进程。类似于模块导入的方式

  • 会从上往下依次执行代码

  • linux中则是直接将代码拷贝一份


  • 第二种,类的继承
from mutiprocessing import Process
import time

class MyProcess(Process):
    def run(self):
        print('hello,bf girl')
        time.sleep(2)
        print('get out!!')
if __name__  == '__main__':
    p=MyProcess()
    p.start()
    print('主进程')

总结:

"""
创建进程就是在内存中申请一块内存空间,将需要运行的代码丢进去
一个进程对应在内存中就是一块独立的内存空间
多个进程对应在内存中就是多块独立的内存空间
进程与进程之间数据默认情况下是无法直接交互,如果想交互可以借助于第三方工具、模块
"""
进程对象的join 方法

join是让主进程的代码等待子进程代码运行结束之后,再继续运行,不影响其他子进程的执行

from multiprocessing import Process
import time

def task(name):
    print('{} is running'.format(name))
    time.sleep(3)
    print('%s is over' % name)
    
if __name__ == '__main__':
    #1.创建一个进程对象
    p=Process(target=task,args=('zhao',))
    #容器类型哪怕里面只有1个元素,建议一定要用逗号隔开
    #2.开启进程
    p.start()  #仅仅是告诉操作系统要创建进程
    p.join()#主进程等待子进程p运行结束之后再继续往后执行
    print('主进程')
start_time=time.time()
for i in range(3):
    p=Process(target=task,args=('子进程%s'%i,i))
    p.start()
    p.join()
print('主',time.time()-start_time)#耗时6秒多
#相当于变成串行,将并发变成串行,程序效率降低
start_time=time.time()
p_list=[]
for i in range(3):
    p=Process(target=task,args=('子进程%s'%i,i))
    p.start()
    p_list.append(p)
    
for p in p_list:
    p.join()
print('主',time.time()-start_time)#耗时3秒多一点

进程之间数据相互隔离(默认情况下)
money=100
def task():
    global money
    money=666
if __name__ == '__main__':
 
    p=Process(target=task)
    p.start()
    p.join()
    print(money) # 100

图灵机器人

科大讯飞

进程对象及其他方法
"""一台计算机上运行着很多进程,那么计算机是如何区分并管理这些进程服务端 的呢

计算机会给每一个运行的进程分配一个PID号
windows,cmd后输入tasklist
tasklist |firsttr  PID  #查看具体的进程

linux:ps aux
	ps aux |grep PID #查看具体的进程
"""

Pycharm激活网站

from multiprocessing import Process,current_process
import time,os


def task():
    #print("%s is running"%current_process().pid)#查看当前进程的进程号
     print("%s is running"%os.getpid())#查看当前进程的进程号
     print("子进程的主进程号是:%s "%os.getppid())#当前进程的父进程号
    time.sleep(3)

    
if __name__ == '__main__':
    p=Process(target=task)
    p.start()
    #p.terminate()#杀死当前进程,告诉操作系统帮你去杀死进程,但是需要时间,而代码的运行速度极快
    #print(p.is_alive())#判断当前进程是否存活
    print('主进程',os.getpid())
    print('主主',os.getppid())#获取父进程的PID
    

一般情况下会默认将存储布尔值的变量名和返回的结果是布尔值的方法名

都起成以is_开头

僵尸进程与孤儿进程
#僵尸进程
"""
死了没死透
当你开设了子进程后,该进程死后不会立即释放占用的进程号
因为要让父进程能够查看到它开设的子进程的一些基本信息,占用的PID号,运行时间,所有的进程都会步入僵尸进程
	父进程不死,并且在无限制的创建子进程并且子进程也不会结束
	回收子进程占用的pid号:
		父进程等待子进程运行结束
		父进程调用join方法
	
"""
#孤儿进程
"""
子进程存活,父进程意外死亡
操作系统会开设一个“儿童福利院”,专门管理孤儿进程回收相关资源
"""

守护进程
from multiprocessing import Process
import time
def task(name='lisi'):
    print('%s总管正常活着'%name)
    time.sleep(2)
    print("%s总管正常死亡"%name)
if __name__ == '__main__':
    p=Process(target=task,kwargs={'name':'zhangsan'})
    p.daemon=True#将进程p设置成守护进程,放在start方法上面才有效,否则直接报错
    p.start() 
 
    print("主进程")
互斥锁

针对多个进程操作同一份数据的时候,会出现数据错乱的问题,

针对上述问题,解决方式就是加锁处理:将并发变成串行,牺牲效率但是保证数据安全

"""模拟强票"""
from multiprocessing import Process,Lock
import json,time
import random 
#查票
def search(i):
   #文件操作读取票数
	with open('data','r',encoding='utf8') as fp:
    	dic= json.load(fp)
   	print('用户%s查询余票:%s'%(i,dic.get('ticket_num')))
    #字典取值不要用[]的形式,推荐使用get,使用get不存在也不报错
   
#买票 1.先查,2.再买
def buy(i):
    #查
    with open('data','r',encoding='utf8') as fp:
    	dic= json.load(fp)
   	#模拟网络延迟
    time.sleep(random.randint(1,3))
    #判读当前是否有余票
    if dic.get('ticket_num') > 0:
        #修改数据库,买票
        dic['ticket_num'] -= 1
        #写入数据库
        with open('data','w',encoding='utf8') as fp:
            json.dump(dic,fp)
        print("用户%s买票成功"%i)
    else:
        print('用户%s买票失败'%i)
    
def run(i,mutex):
    search(i)
    #买票环节加锁处理
    #抢锁
    mutex.acquire()
    buy(i)
    #释放锁
    mutex.release()
if __name__ == '__main__':
    #在住进程中生成一把锁,让子进程抢,谁先抢到谁买票
    mutex=Lock()
    for i in range(1,10):
        p=Process(target=run,args=(i,mutex))
        p.start()

        """
        扩展:行锁,表锁
        	行锁:操作表格中的一行数据的时候其他人都不能操作
        	表锁;操作一张表的时候其他人都不能操作
        注意:
        	1.锁不要轻易的使用,容易造成死锁现象,都是内部封装好的
        	2.锁只在处理数据的部分呢加,保证数据安全(只在 争抢数据的环节加锁处理即可)
        """
进程间通信(Queue)

进程之间是无法直接进行数据交互的,但是可以通过队列活着管道实现数据交互

q.put()#往队列中存数据
q.get() #队列中如果已经没有数据的话,get方法会原地阻塞
print(q.full())#判断当前队列是否满了
print(q.empty())#判断当前队列是否空了
print(q.qsize())#队列里数据的个数
q.get_nowait()#没有数据直接报错queue.Empty
q.get(timeout=3)#没有数据之后原地等待3秒再报错

队列Queue模块

"""
管道:subprocess   
	stdin  stdout stderr
队列:管道+锁


队列:先进先出
堆栈:先进后出

"""
#import queue 
#创建队列
#q=queue.Queue(5)#括号内可以传数字,表示生成的队列最大同时可以存放的数据量
from multiprocessing import Queue
q=Queue(5)

#往队列中存数据
q.put(111)
q.put(222)
q.put(333)
print(q.full())#判断当前队列是否满了
print(q.empty())#判断当前队列是否空了
q.put(444)
q.put(555)
print(q.qsize)#队列里数据的个数
#q.put(666)#当队列数据放满了之后,,如果还有数据要放,程序会阻塞,直到有位置让出来,不会报错
"""
存取数据:存是为了更好的取
千方百计的存,简单快捷的取
"""
#去队列中取数数据
v1=q.get()
v2=q.get()
v3=q.get()
v4=q.get()
v5=q.get()
try:
    v6=q.get(timeout=3)
    print(v6)
except Exception as e:
    print(e)


#v6=q.get_nowait()#没有数据直接报错queue.Empty
#v6=q.get(timeout=3)#没有数据之后原地等待3秒再报错
#v6=q.get()#队列中如果已经没有数据的话,get方法会原地阻塞
print(v1,v2,v3,v4,v5,v6)



"""
q.full()
q.empty()
q.get_nowait()
上面这三个方法对进程的情况下是不精确的

"""
IPC机制
"""
研究思路:
	1.主进程跟子进程借助于队列通信
	2.子进程跟子进程借助于队列通信
"""
"""主进程跟子进程借助于队列通信"""
from multiprocessing import Queue,Process
def producer(q):
    q.put('燕子李三')
    print('hello')
    
if __name__ == '__main__':  
    q=Queue()
    p=Process(target=producer,args=(q,))
    p.start()
    print(q.get())
  
"""子进程跟子进程借助于队列通信"""
from multiprocessing import Queue,Process
def producer(q):
    q.put('燕子李三')
    
def consumer(q):
    print(q.get())
if __name__ == '__main__':  
    q=Queue()
    p=Process(target=producer,args=(q,))
    p1=Process(target=consumer,args=(q,))
    p.start()
    p1.start()
生产者消费者模型
"""
生产者;
	生产/制造东西的
消费者:
	消费/处理东西的

该模型除了上述两个之外还需要一个媒介
	生活中的例子做包子,将包子做好之后放在蒸笼里(媒介),买包子的取蒸笼里面拿
	厨师做菜做完之后用盘子装着给你消费者递过去
	生产者和消费者之间不是直接做交互的,而是借助于媒介做交互
生产者(做包子)  + 消息队列 (蒸笼) + 消费者(客人)
 

"""
from multiprocessing import Process,Queue
import time,random


def producer(name,food,q):
   for i in range(5):
    	data='%s生产了%s,%s'%(name,food,i)
        time.sleep(random.randint(1,3))
        print(data)
        q.put(data)
    
def consumer(name,q):
    while True:
        food=q.get()
        #判断当前是否有结束的表示
        if food is None:break
        time.sleep(random.randint(1,3))
        print('%s吃了%s'%(name,food))
 
if __name__ == '__main__':  
    q=Queue()
    p1=Process(target=producer,args=('大厨','包子',q))
    p2=Process(target=producer,args=('张桑','养生汤',q))
    c1=Process(target=consumer,args=('李四',q))
    c2=Process(target=consumer,args=('王五',q))
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    
    p1.join()
    p2.join()
    #等待生产者生产完毕之后,往队列中添加特定的结束符号
    q.put(None)#肯定在所有生产者生产的数据末尾
    q.put(None)
    #有两个消费者所以要放两个None
    
from multiprocessing import Process,JoinableQueue
import time,random


def producer(name,food,q):
   for i in range():
    	data='%s生产了%s,%s'%(name,food,i)
        time.sleep(random.randint(1,3))
        print(data)
        q.put(data)
    
def consumer(name,q):
    while True:
        food=q.get()
  
       
        time.sleep(random.randint(1,3))
        print('%s吃了%s'%(name,food))
        q.task_done()#告诉队列你已经从里面取出了一个数据并且处理完毕了
 
if __name__ == '__main__':  
    q=JoinableQueue()
    p1=Process(target=producer,args=('大厨','包子',q))
    p2=Process(target=producer,args=('张桑','养生汤',q))
    c1=Process(target=consumer,args=('李四',q))
    c2=Process(target=consumer,args=('王五',q))
    p1.start()
    p2.start()
    #将消费者设置成守护进程
    c1.daemon=True
    c2.daemon=True
    c1.start()
    c2.start()
    
    p1.join()
    p2.join()
	q.join()#等待队列中所有的数据被取完再往下执行代码
"""
JoinableQueue 每当往队列中存入数据的时候,内部会有一个计数器+1
每当调用task_done的时候,计数器-1
q.join()当计数器为0的时候,才往后运行
"""
#只要q.join()执行完毕,说明消费者已经执行完毕了,消费值已经没有存在的意义了

03、线程

  • 什么是线程

    """
    进程是资源单位(开启一个进程仅仅是在内存空间中开启一块独立的内存空间)
    线程是执行单位(真正CPU执行的其实是进程里面的线程,线程指的就是代码的执行过程,执行代码中所需要使用到的资源都向所在的进程所要)
    
    将操作系统比喻成一大的工厂,而进程 相当于工厂里面的车间
    而线程是车间里面的流水线
    
    每一个进程肯定自带一个线程
    
    进程和线程都是虚拟单位,只是为了我们更方便的描述问题
    """
    
    
    • 为何要有进程

      """
      开设进程
      	1.申请内存空间,耗费资源
      	2. ‘拷贝代码’  耗费资源
      开设线程
      	一个进程可以开设多个线程,在同一个进程内开设多个线程无需申请内存空间及拷贝代码的操作,
      	开设线程的开销远远小于进程的开销
      """
      
    开启线程的两种方式
    from threading import Thread
    def task(name):
    	print("%s is  running "%name)
    #开启线程不需要在main下面执行代码,直接书写就可以
    #但是还是习惯性的将启动命令写在main下面
    t=Thread(target=task,args=('zhao',))
    t.start()#创建线程的开销非常小,几乎代码一执行线程就已经创建了
    print('主')
    
    from threading import Thread
    import time
    class MyThread(Thread):
        def __init__(self,name):
            #重写别人的方法,又不知道别人的方法里面有啥,就调用父类的方法
            super().__init__()
            self.name=name
        def run(self):
            print("%s is running "%self.name)
            time.sleep(3)
            print('lisi')
    if __name__ == '__main__':  
        t=MyThread('zhao')
        t.start()
        print('主')
    
    TCP服务端实现并发的效果
    import socket
    from threading import Thread
    from multiprocessing import Process
    """
    服务端:
    	1. 固定的IP和端口
    	2. 24小时不间断提供服务
    	3. 能够支持并发
    	
    """
    
    server =socket.socket()#括号内不加操作默认就是TCP协议
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    #将服务的代码单独封装成一个函数
    def talk(conn):
        #通信循环
        while True:
            try:
            	data=conn.recv(1024)
                #针对mac.linux当断开链接后,频繁收到空
                if len(data) == 0:break
               	print(data.decode('utf-8'))
                conn.send(data.upper())
            except ConnectionResetError as e:
                print(e)
                break
    	conn.close()
        
    
    #链接循环
    while True:
        conn,addr=server.accept()#接客
        #叫其他人来服务
        t=Thread(target=talk,args=(conn,))
        t.start()
        
            
            
    
    import socket
    client=socket.socket()
    client.connect(('127.0.0.1',8080))
    while True:
        client.send(b'hello')
        data=client.recv(1024)
        print(data.decode('utf-8'))
    
    
线程对象的join方法
from threading import  Thread
import time

def task(name):
    print('%s is running '%name)
    time.sleep(2)
    print("%s is over"%name)
if __name__ == '__main__':
    t=Thread(target=task,args=('zhao',))
    t.start()
    t.join()#主线程等待子线程运行结束再执行
    print('主')
同一个进程下的多个线程是共享的
from threading import Thread
import time 

money =100
def task():
    global money
    money=666
    print(money)
if __name__ == '__main__':
    t=Thread(target=task)
    t.start()
    t.join()
    print(money)
线程对象及其他方法
from threading import Thread,active_count,current_thread
import time,os

def task(n):
    #print('hello,world!!',os.getpid())
     print('hello,world!!',current_thrad().name)#获取当前线程的名字
    
if __name__ == '__mian__':
    t=Thread(target=task,args=(1,))
    t1=Thread(target=task,args=(2,))
    t.start()
    t1.start()
    t1.join()
    print('主',active_count())#统计当前正在活跃的线程数
    print('主',os.getpid())
    print('主',current_thrad().name)#主线程 Main Thread
守护线程
from threading import Thread
import time

def task(name):
    print('%s is running '%name)
    time.sleep(1)
    print("%s is over"%name)
if __name__ == '__main__':
    t=Thread(target=task,args=('zhao',))
    t.daemon=True
    t.start()
    print('主')
    
    
"""
主线程运行结束之后不会立即结束,会等待其他非守护线程结束才会结束
主线程结束意为着所在进程的结束
"""
from threading import Thread
import time,os

def task():
    print(132)
    time.sleep(1)
    print('end123')
    
def func():
    print(456)
    time.sleep(3)
    print('end465')
if __name__ == '__main__':
    t1=Thread(target=task)
    t2=Thread(target=func)
    t1.daemon=True
    t1.start()
    t2.start()
    print('主')
    
#守护线程:主线程运行结束,守护线程也跟着结束

    
"""
 运行流程:
 	t1.start()立马打印132,t2.start()立马打印456,然后打印"主",但是主线程不会立刻结束,它要等待所有的非守护线程结束才能结束,task睡1秒,func睡3秒,task睡1秒。立马打印end123,然后打印end456,


打印结果:	
 	132
 	456
 	主
 	end123
 	end456
    
"""
线程互斥锁
from threading import Thread,Lock
import time

money=100
mutex=Lock()    

def task():
	global money
    mutex.acquire()
    tmp=money
    time.sleep(0.1)
    money=tmp - 1
    mutex.release()

if __name__ =="__main__":
    
    t_list=[]
    for i in range(100):
        t=Thread(target=task)
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print(money)
    
    

GIL全局解释器锁🔒
"""
In CPython,the global interpreter lock , or GIL, is a mutex that prevents multiple
native thrads from executing Python bytecodes at once,This lock is  necessary mainly
beacuse CPython's memory management is not thread-safe.(However, since the GIL exists, other features have grown to depend on the guarantees that is enforces)

"""
"""
Python解释器其实有多个版本
	Cpython
	Jpython
	Pypypython
但是最普遍使用的还是Cpython解释器

在Cpython解释器中,GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行
同一个进程下的多个进程无法利用多个优势!!!
因为CPython中的内存管理不是线程安全的,
内存管理(垃圾回收机制)
	1. 引用计数
		
	2. 标记清除
	3. 分代回收

重点:
	1. GIL不是Python的特点,而是Cpython解释器特点
	2. GIL保证解释器级别的数据的安全
	3. GIL会导致同一个进程下的多个线程无法同时执行即无法利用多个优势
	4. 针对不同的数据还是需要加不同的锁处理
	5. 解释型语言的通病:同一个进程下多个线程无法利用多核优势
	
"""
GIL与普通解释器的区别
from threading import Thread,Lock
import time

mutex=Lock()
money=100


def task():
	global money   
    #with mutex:
    #    tmp = money
    #    time.sleep(0.1)
    #    money = tmp - 1
    mutex.acquire()
    tmp=money
    time.sleep(0.1)#只要进入IO,GIL会自动释放,
    money=tmp - 1
    mutex.release()
   

if __name__ =="__main__":
    
    t_list=[]
    for i in range(100):
        t=Thread(target=task)
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print(money)    
    
    """
    100个线程开启后,先去抢GIL
    进入IO,GIL自动释放,但是还有一个互斥锁mutex
    其他线程虽然抢到了GIL,但是抢不到互斥锁
    最终GIL还是回到自己手上,自己去操作数据
    
    
    """
同一个进程下的多进程无法利用多核优势是不是就没有用
"""
多线程是否有用要具体看情况
单核:四个任务(IO密集型\计算密集型)
多核:四个任务(IO密集型\计算密集型)

"""
#计算密集型   每个任务都需要10s
单核:
	多进程:额外的消耗资源
    多线程:减少开销
多核:
	多进程:总耗时10s+
    多线程:总耗时40s+
    
#IO 密集型   每个任务都需要10s
多核:
	多进程:相对浪费资源
    多线程:更加节省资源
#计算密集型
from multiprocessing import Process
from threading import Thread
import os ,time

def work():
    res=0
    for i in range(1000000000):
		res*=i
if __name__ =='__main__':
    l=[]
    print(os.cpu_count())#获取当前计算机CPU个数
    start_time=time.time()
    for i in range(12):
        #p=Process(target=work)
        #p.start()
        #l.append(p)
        t=Thread(target=work)
        t.start()
        l.append(l)
        
   	for p in l:
        p.join()
    print(time.time()-start_time)    
#IO 密集型
from multiprocessing import Process
from threading import Thread
import os ,time

def work():
    time.sleep(2)
if __name__ =='__main__':
    l=[]
    print(os.cpu_count())#获取当前计算机CPU个数
    start_time=time.time()
    for i in range(40000):
        #p=Process(target=work)
        #p.start()
        #l.append(p)
        t=Thread(target=work)
        t.start()
        l.append(l)
        
   	for p in l:
        p.join()
    print(time.time()-start_time) 

总结:

"""
多进程和多线程都有各自的优势
并且再后面写项目都是多进程下面再开设多线程
这样的话既可以利用多核也可以减少资源消耗
"""
死锁(了解)
  • 开发过程中使用线程,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁

  • 尽管死锁很少,但是一旦发生了就会造成应用的停止响应,程序不会做任何事

  • 当知道锁的使用抢锁必须要释放锁,其实在操作锁的时候也容易产生死锁现象(整个程序卡死,阻塞)

from threading import Thread,Lock
import time

mutexA=Lock()
mutexB=Lock()
#类只要加括号多次,产生的肯定是不同的效果
#如果想要实现多次加括号等到的是相同的对象,单例模式

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()
        
    def func1(self):
        mutexA.acquire()
        print('%s 抢到A锁'%self.name)#获取当前线程名
        mutexB.acquire()
        print('%s 抢到B锁'%self.name)
        mutexB.release()
        mutexA.release()
        
    def func2(self):
        mutexB.acquire()
        print("%s 抢到B锁"%self.name)
        time.sleep(2)
        mutexA.acquire()
        print("%s 抢到A锁"%self.name)
        mutexA.release()
        mutexB.release()
        
if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()
递归锁(了解)

递归锁的特点:

  • 可以连续的acquire和release
  • 但是只能被第一个抢到这把锁执行上述操作
  • 它的内部有一个计数器,每acquire一次,计数器+1,每release一次,计数器-1
  • 只要计数器不为0,那么其他人都无法抢到该锁
from threading import Thread, Lock,RLock
import time

# mutexA = Lock()
# mutexB = Lock()
mutexA = mutexB = RLock()


class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print('%s 抢到A锁' % self.name)  # 获取当前线程名
        mutexB.acquire()
        print('%s 抢到B锁' % self.name)
        mutexB.release()
        mutexA.release()

    def func2(self):
        mutexB.acquire()
        print("%s 抢到B锁" % self.name)
        time.sleep(2)
        mutexA.acquire()
        print("%s 抢到A锁" % self.name)
        mutexA.release()
        mutexB.release()


if __name__ == '__main__':
    for i in range(10):
        t = MyThread()
        t.start()
信号量(了解)

信号量再不同的阶段,可能对应不用的技术点

在并发编程中信号量指的是锁

"""
如果把互斥锁比喻成一个厕所
那么信号量相当于多个厕所
"""
from threading import Thread ,Semaphore
import time
import random
sm=Semaphore(5)#括号内写几,就表示开设几个坑位
def task(name):
    sm.acquire()
    print("%s 正在忙"%name)
    time.sleep(random.randint(1,5))
    sm.release()
if __name__ == '__main__':
    for i in range(20):
		t=Thread(target=task,args=('伞兵%s号'%i,))
		t.start()
		
    

Event事件(了解)

一些进程或者线程需要等待另外一些进程或者线程运行完毕之后才能运行,类似于发射信号一样

from threading import Thread,Event
import time

event = Event()#类似于造了一个红绿灯
def light():
    print("红灯亮")
    time.sleep(3)
    print("绿灯亮")
    #告诉等待红灯的人可以走了
    event.set()
 
def car(name):
    print("%s 车正在等红灯"%name)
    event.wait()#等待别人给你发消息
    print('%s 车加油门走了'%name)

if __name__ == '__main__':
	t=Thread(target=light)
    t.start()
    
    for i in range(20):
        t=Thread(target=car,args=('%s'%i,))
        t.start()
    
	

线程q(了解)

Python的queue模块提供了同步的,线程安全的队列类,包括FIFO(first in first out 先进先出)队列Queue,LIFO(last in first out后进先出)队列LifoQueue和优先级队列PriorityQueue。这些队列都实现了锁的原理(可以理解为原子操作,即要么不做,要么就做完),能够在线程中直接使用。

可以使用队列来实现线程间的同步。

"""
同一个进程下多个线程数据是共享的
为什么在同一个进程下还会使用队列呢?
因为队列是管道+锁组成的
所以用队列还是为了保证数据的安全
"""
from threading import Thread
import queue
#现在使用的队列都是只能在本地测试使用

#1.队列q 先进先出
q=queue.Queue(3)
q.put(1)
q.put(2)
print(q.get())#1
q.get_nowait()
q.get(timeout=3)
q.full()
q.empty()

# 2.后进先出q 
q=queue.LifoQueue(3)  # last in first out
q.put(1)
q.put(2)
print(q.get()) #2


#3.优先级q  可以给放入队列中的数据设置进出的优先级
#put括号内放元组,第一个数字表示优先级
#数字越小,优先级越高
q=queue.PriorityQueue(3)
q.put((10,'111'))
q.put((100,'222'))
q.put((0,'333'))
print(q.get())    #(0,'333')数字越小,优先级越高

04、池

  • 用来保证计算机硬件安全的情况下最大限度的利用计算机
  • 它降低了程序的运行效率,但是保证了计算机硬件的安全,从而让写的程序能正常运行
线程池与进程池(掌握)
"""
无论开设进程还是线程都是需要消耗资源的
只不过开设线程的消耗比开设进程的消耗稍微小一点而已

我们是不可能无限制的开设进程和线程,因为计算机硬件的资源跟不上 ,硬件的开发速度远远赶不上软件

宗旨:保证在计算机硬件能够正常工作的情况下最大限度的利用它
"""

第一种使用

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
pool = ProcessPoolExecutor(5)
res = pool.submit(task, i).add_done_callback(callback)
  • 只需要将需要做的任务往池子里面仍,自动会有人来服务你
from concurrent.futures import ThreadPoolExecutor, 
ProcessPoolExecutor
import time

pool = ThreadPoolExecutor(5)  # 池子里面固定有5个线程
# 括号内可以穿数字,不穿的话会默认开设当前计算机cpu个数五倍的线程
"""
池子造出来后,里面会固定存5个线程
这五个线程不会出现重复创建和销毁的过程
"""


def task(n):
    print(n,os.getpid())
    time.sleep(2)
    return n**n
"""
任务的提交方式:
	同步:提交任务后会原地等待任务的返回结果,期间不会做任何事
	异步:提交任务后不等待任务的返回结果,执行继续往下执行
	
异步提交任务的返回结果,应该通过回调机制来获取
一旦该任务有结果就立即触发
"""
# poo.submint(task,1)#朝池子中提交任务    异步提交
# print('主')

t_list = []
for i in range(20):  # 往池子中提交30个任务
    res = pool.submit(task, i)
    
    # print(res.result())#同步提交
    t_list.append(res)
# 等待池子中所有的任务执行完毕后再继续往下执行
	pool.shutdown()  # 关闭线程池,等待线程池中所有的任务执行完毕
	for t in t_list:
	    print(">>>", t.result())  # 结果是有序的
	
	    """
	    res.result()拿到的是异步提交返回的结果
	    """

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time, os

pool = ProcessPoolExecutor(5)
# 括号内可以穿数字,不穿的话会默认开设当前计算机cpu个数
"""
池子造出来后,里面会固定存几个进程
这几个进程不会出现重复创建和销毁的过程
"""


def task(n):
    print(n, os.getpid())
    time.sleep(2)
    return n ** n


def callback(n):
    print('callback>>', n.result())


"""
任务的提交方式:
	同步:提交任务后会原地等待任务的返回结果,期间不会做任何事
	异步:提交任务后不等待任务的返回结果,执行继续往下执行
"""
if __name__ == '__main__':

    t_list = []
    for i in range(20):  # 往池子中提交30个任务
        # res = pool.submit(task, i)
        res = pool.submit(task, i).add_done_callback(callback)
        # print(res.result())#同步提交
        t_list.append(res)
        # 等待池子中所有的任务执行完毕后再继续往下执行
        # pool.shutdown()  # 关闭线程池,等待线程池中所有的任务执行完毕
        # for t in t_list:
        #     print(">>>", t.result())  # 结果是有序的

        """
        res.result()拿到的是异步提交返回的结果
        """

第二种使用

from multiprocessing import  Pool
pool=Pool(max)#创建进程池对象
pool.apply()#阻塞
pool.apply_async()#非阻塞
pool.close()
pool.join()#主进程等待其他进程结束
  • 非阻塞式
"""

非阻塞式:全部添加到队列,立刻返回,没有等待其他进程执行完毕之后才返回

"""
from multiprocessing import Pool
import time, random
import os


# 非阻塞式进程
def task(name):
    print('开始任务:', name)
    start = time.time()
    time.sleep(random.random() * 2)
    end = time.time()
    # print('完成任务:{},用时:{},进程ID:{}'.format(name, (end - start), os.getpid()))
    return '完成任务:{},用时:{},进程ID:{}'.format(name, (end - start), os.getpid())


containner = []


def callback_func(n):
    containner.append(n)


if __name__ == '__main__':
    pool = Pool(5)
    tasks = ['听音乐', '打篮球', '洗衣服', '打游戏', '吃饭', '睡觉', '散步']
    for task1 in tasks:
        pool.apply_async(task, args=(task1,), callback=callback_func)  # 非阻塞

    pool.close()  # 添加任务结束
    pool.join()

    for c in containner:
        print(c)
    print('over')

"""
进程池:

"""
  • 阻塞式
import os
import random
from multiprocessing import  Pool
import  time


def task(name):
    print('开始任务:', name)
    start = time.time()
    time.sleep(random.random() * 2)
    end = time.time()
    print('完成任务:{},用时:{},进程ID:{}'.format(name, (end - start), os.getpid()))
    # return '完成任务:{},用时:{},进程ID:{}'.format(name, (end - start), os.getpid())

"""
阻塞式:
添加一个执行一个,如果一个任务不结束,另一个任务进不来

"""


if __name__ == '__main__':
    pool = Pool(5)
    tasks = ['听音乐', '打篮球', '洗衣服', '打游戏', '吃饭', '睡觉', '散步']
    for task1 in tasks:
        pool.apply(task, args=(task1,))  # 非阻塞

    pool.close()  # 添加任务结束
    pool.join()


    print('over')

05、协程

微线程

"""
进程:资源单位,
线程:执行单位,
协程:这个概念完全是程序员意淫出来的,根本不存在
	单线程下实现并发的效果
	我们程序员自己在代码层面上自己监测所有的IO操作,一旦遇到IO,我们在代码级别完成切换
	这样给CPU的感觉是你这个程序一直在运行,没有IO,从而提升运行效率
多道技术:
	切换+保存状态
	CPU两种切换:
		1.程序遇到IO
		2.程序长时间占用


进程》线程》协程
"""
greenlet模块(了解)
import time

import greenlet


# 完成协程任务
def a():
    for i in range(5):
        print('A' + str(i))
        gb.switch()
        time.sleep(0.2)


def b():
    for i in range(5):
        print('B' + str(i))
        gc.switch()
        time.sleep(0.2)


def c():
    for i in range(5):
        print('C' + str(i))
        ga.switch()
        time.sleep(0.2)


if __name__ == '__main__':
    ga = greenlet.greenlet(a)
    gb = greenlet.greenlet(b)
    gc = greenlet.greenlet(c)
    ga.switch()


gevent模块(了解)
pip install gevent
  • 本身无法监测常见的一些IO操作

  • 导入的时候需要额外导入

from gevent import monkey
monkey.patch_all()

由于上面的两句话在使用gevent模块的时候肯定要导入的,所以支持简写

from gevent import monkey;monkey.patch_all()

使用:

from gevent import spawn
from gevent import monkey
import time

def info():
    print('哈哈哈')
    time.sleep(2)
    print("黑恶hi黑")
    
def msg():
    print('英语')
    time.sleep(2)
    print("数学")  
    
start_time=time.time()
g1=spawn(info)
g2=spawn(msg)
g1.join()
g2.join()#等待被监测的任务执行完毕再往后继续执行
#info()
#msg()
print(time.time()-start_time)
  • greenlet已经实现了协程,但是人工切换,太麻烦,
  • 当greenlet遇到IO,(指的是input output输入输出,)比如(网络,文件操作等)操作时
  • 比如访问网络,就自动切换到其他的greenlet,等到IO完成,再适合的时候切换回来继续执行
  • 由于IO操作非常耗时,经常使程序处于等待状态,有了gevent可以自动切换协程,就保证总有greenlet在运行,而不是等待IO
"""


"""
import time
from gevent import monkey
import gevent

monkey.patch_all()#打补丁

# 完成协程任务
def a():
    for i in range(5):
        print('A' + str(i))

        time.sleep(0.2)


def b():
    for i in range(5):
        print('B' + str(i))

        time.sleep(0.2)


def c():
    for i in range(5):
        print('C' + str(i))

        time.sleep(0.2)


if __name__ == '__main__':
    g1 = gevent.spawn(a)
    g2 = gevent.spawn(b)
    g3 = gevent.spawn(c)
    
    g1.join()
    g2.join()
    g3.join()

协程实现TCP服务端并发效果
"""服务端"""
from gevent import monkey;monkey.patch_all()
import socket
from gevent import spawn


def comunication(conn):
    # 通信循环
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0: break
            conn.send(data.upper())
        except ConnectionResetError as r:
            print(r)
            break
    conn.close()


def server(ip, port):
    server = socket.socket()
    server.bind((ip, port))
    server.listen(5)
    while True:
        # 链接循环
        conn, client_addr = server.accept()
        # 开设多进程或多线程进行客户端通信
        spawn(comunication, conn)


if __name__ == '__main__':
    g1 = spawn(server, '127.0.0.1', 8080)
    g1.join()

"""客户端"""
import socket
from threading import Thread,current_thread


def client():
    client = socket.socket()
    client.connect(('127.0.0.1', 8080))
    n = 1
    while True:
        msg='%s say hello %s'%(current_thread().name,n)
        n+=1
        client.send(msg.encode('utf-8'))
        data=client.recv(1024)
        print(data.decode('utf-8'))
if __name__ == '__main__':
    for i in range(200):
        t=Thread(target=client)
        t.start()

总结:

理想状态:

  • 可以通过对进程下面开设多进程
  • 可以通过多进程下面再开设协程
  • 从而我们的执行效率提升

06、IO模型

详见相关博客

非阻塞IO

"""server.py"""
# -*- coding: UTF-8 -*-  @Date :2022/9/23 17:21
import socket, time

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
server.setblocking(False)  # 默认是true,
# 将网络阻塞变成非阻塞

r_List = []
del_List = []
# 链接循环
while True:
    try:
        conn, addr = server.accept()
        r_List.append(conn)

    except BlockingIOError as e:
        print('列表长度', len(r_List))
        # print('............')
        for conn in r_List:
            try:
                data = conn.recv(1024)  # 没有消息报错
                if len(data) == 0:  # 客户端关闭链接
                    conn.close()  # 关闭conn
                    # 将无用的conn从r_list删除
                    del_List.append(conn)
                    continue
                conn.send(data.upper())
            except BlockingIOError:
                continue
            except ConnectionResetError:
                conn.close()
                del_List.append(conn)
        for conn in del_List:
            r_List.remove(conn)
        del_List.clear()

"""client.py"""
# -*- coding: UTF-8 -*-  @Date :2022/9/23 17:28
import socket


client=socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    client.send(b'hello world')
    data=client.recv(1024)
    print(data)

"""
非阻塞IO模型会长时间占用CPU并且不干活,让CPU不停的空转
实际应用中也不会用非阻塞IO模型
但是任何一个技术点都有它存在的价值
”实际应用或者思想借鉴"
"""

多路复用IO


"""
当监管的对象只有一个的时候,其实IO多路复用连阻塞IO都比不上!!!
但是IO多路复用可以一次性监管多个对象

server=socket.socket()
conn,addr=server.accept()

监管机制是操作系统本身就有的,如果想用监管机制(select)需要导入select模块
"""
"""server.py"""
# -*- coding: UTF-8 -*-  @Date :2022/9/23 18:02

import socket
import select

server = socket.socket()
server.bind(("127.0.0.2", 8080))
server.listen(4)
server.setblocking(False)  # 将所有阻塞变成非阻塞

read_list = [server]

while True:
    r_list, w_list, x_list = select.select(read_list, [], [])
    # res = select.select(read_list, [], [])
    """
    监管对象,一旦有人来了,就返回对应的监管对象
    """
    # print(res)  # ([<socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.2', 8080)>], [], [])
    # print(server)  # <socket.socket fd=768, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.2', 8080)>

    for i in r_list:
        if i is server:
            conn, addr = i.accept()
            # 应该把conn添加到监管的队列中去
            read_list.append(conn)
        else:
            res = i.recv(1024)
            if len(res) == 0:
                i.close()
                # 将无效的监管对象移除
                read_list.remove(i)
                continue
            print(res)
            i.send(b"heiheiheiheihei")

"""client.py"""
# -*- coding: UTF-8 -*-  @Date :2022/9/23 18:02
import socket

client = socket.socket()
client.connect(("127.0.0.2", 8080))

while True:
    client.send(b'hello world')
    data = client.recv(1024)
    print(data)

"""
监管机制有很多
select机制:windows Linux都有
poll机制:只在Linux有,

poll和select都可以监管多个对象,但是poll监管的数量更多

上述select和poll机制都不是很完美,当监管对象特别多的时候可能会出现及其大的延迟响应

epoll机制:只在Linux有
它给每一个监管对象都绑定一个回调机制一旦有响应,回调机制立刻发起提醒


针对不同的操作系统还需要考虑不同的检测机制,代码书写太过繁琐

selectors模块会根据代码跑的平台的不同自动的选择对应的监管机制


"""

异步IO

"""
异步IO模型是所有模型中效率中最高的,也是使用最广泛的

相关模块和框架
	模块:asyncio模块
	框架:sanic,tronado twisted
		速度快!!!


"""
# -*- coding: UTF-8 -*-  @Date :2022/9/23 18:29

import asyncio
import threading


@asyncio.coroutine
def hello():
    print('hello', threading.current_thread().name)
    yield from asyncio.sleep(1)
    print('hello', threading.current_thread().name)


loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

过期的秋刀鱼-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值