python并发编程

并发编程相关概念

# 进程
"""
程序运行的过程,是操作系统资源分配的最小单位.资源分配的是cpu和内存的物理资源.
进程号(PID):进程的号码,不同进程有不同的号码.进程之间,彼此隔离,通过socket通信
"""
# 进程三状态: 就绪-----运行-----阻塞 (在阻塞状态下,等时间片到了时,转为运行状态)
​
# 并发: 一个cpu同一时间执行多个任务
​
# 并行: 多个cpu同一时间执行多个任务"""
​
# cpu进程的调度方法:1, 先来的先执行  2, 短作业优先  3, 时间片轮转  4, 多级反馈
​
# 同步: 一条主线,调用一个方法,要等这个方法执行结束,再开始下一个
​
# 异步: 多条主线,不在一条主线上的方法,不关心,齐头并进

  

1, 进程的基本使用

# 获取进程号
import
os.getpid()                     # 获取当前进程号(子进程)
os.getppid()                    # 获取父进程
"""父子是相对的
pycharm    和     py
那么pycharm是父进程,py文件是子进程
py     和     func
那么py文件是父进程, func 是子进程
"""
# linux 命令
ps -aux                         # 获取所有进程列表
ps -aux | grep 进程号码          # 获取单独进程号进程具体信息
kill -9 进程号码                 # 杀死进程号码进程

  

1.1.1,在进程里边创建子进程

import time,os
from multiprocessing import Process
​
# 曾经的代码
def func():
    print(os.getpid(),"start")            # 29840 start
    time.sleep(1)                         
    print(os.getpid(),"end")              # 29840 end
if __name__ == "__main__":
    func()
    print("主进程",os.getpid())            # 主进程 29840
    
# 创建子进程
def func():
    print(os.getpid(),"start")            # 29855 start
    time.sleep(1)
    print(os.getpid(),"end")              # 29855 end
if __name__ == "__main__":
    p = Process(target = func)            # 创建一个即将要执行func函数的子进程对象
    p.start()                             # 异步开启子进程
    print("主进程",os.getpid())            # 主进程 29854
    
"""
总结:
     曾经我们的代码时在一个主进程中,代码从上至下一次执行;今天,通过Process能够在
主进程中再创建子进程,start()异步开启子进程,不关心这个进程结束没有,主进程继续执行
而且,我们看到主进程一般情况要比子进程快一点,子进程的顺序却是不同的!(抢占cpu,谁快谁先执行)
"""

  

1.1.2 带参数的子进程

import time,os
from multiprocessing import Process
​
def func(n):
    time.sleep(0.5)
    print(n, "-----", os.getpid())
​
if __name__ == "__main__":
    for i in range(10):
        p = Process(target=func, args=(i,))           # args接收参数,为元组!
        p.start()
    print("主进程", os.getpid())

  

1.1.3 进程之间数据隔离

# 主进程负责开启子进程,也负责回收子进程.主进程代码执行完毕,不代表主进程结束!如果不回收,则子进程称之为僵尸进程.几乎不存在,底层封装好了!
count = 10
def func():
    global count
    count += 1
    print("我是子进程count={}".format(count))
​
if __name__ == "__main__":
    p=Process(target=func)
    p.start()                                           # 我是子进程count=11
    time.sleep(1)
    print(count)                                        # 10

  

1.1.4 多进程异步并发

def func(n):
    print("数字{}<=>1.子进程id>>{},2父进程id>>{}".format(n,os.getpid(),os.getppid()))
​
if __name__ == "__main__":
    for i in range(1,11):
        Process(target=func,args=(i,)).start()
        
    print("主进程执行结束了....",os.getpid())

  

1.1.5 join 同步子父进程

# 只有一个子进程时
def func():
    time.sleep(0.5)
    print("子进程", os.getpid())
​
if __name__ == "__main__":
    p = Process(target=func)
    p.start()                   # 异步,非阻塞
    p.join()                    # 同步阻塞
    print("主进程", os.getpid())
"""
如果没有p.join(),先打印"主进程",再打印"子进程"
p.join()后,会等子进程结束后在执行主进程
"""
​
# 多个子进程怎么执行呢?
​
def func(n):
    time.sleep(0.5)
    print(n, "-----", os.getpid())
​
if __name__ == "__main__":
    lst = []
    for i in range(10):
        p = Process(target=func, args=(i,))
        p.start()         
        lst.append(lst)
        
    for j in lst:
        p.join()                  # 已结束的进程相当于pass,没有执行的进行阻塞
    print("主进程", os.getpid())

  

1.1.6 守护进程

"""
守护进程守护的是主进程,如果主进程中的所有代码执行完毕了,
当前这个守护进程会被立刻杀死,立刻终止.
语法:
进程.daemon = True 设置当前这个进程为守护进程
必须写在start()调用进程之前进行设置
默认:主进程会默认等待所有子进程执行结束之后,在关闭程序,释放资源
"""
def func1():
    count = 1
    while True:
        print("*" * count)
        time.sleep(0.5)
        count += 1
        
def func2():
    print("start func2 当前子进程任务")
    time.sleep(3)
    print("end   func2 当前子进程任务")
​
if __name__ == "__main__":
    p1 = Process(target=func1)
    p2 = Process(target=func2)
    # 设置p1这个进程对象为守护进程
    p1.daemon = True
    
    p1.start()
    p2.start()
    
    time.sleep(1)
    print("主进程执行结束 ... ")

  

1.1.7 面向对象创建子进程

class MyProcess(Process):
    def __init__(self,arg):
        # 手动调用一下父类的构造方法(最终实现进程的创建)
        super().__init__()
        self.arg = arg
​
    def run(self):  
        print("1.子进程id>>{},2父进程id>>{}".format(os.getpid(),os.getppid()))
        print(self.arg)
    
if __name__ == "__main__":
    p = MyProcess("我是传进来的参数")
    p.start()
    print("3.子进程id>>{},4父进程id>>{}".format(os.getpid(),os.getppid()))

  

1.2 Lock & Semaphore 进程锁

"""
如果不上锁,执行代码,我们会发现,只有一张票的情况下,3个人却都买到了票:
异步并发,会造成数据混乱!
"""
import json,time
from multiprocessing import Process
​
def w_r_info(mod,dic=None):
    if mod == "r":
        with open("12306",mode=mod,encoding='utf-8') as f:
            dic = json.load(f)
            return dic
    else:
        with open("12306",mode=mod,encoding='utf-8') as f:
            json.dump(dic,f)
​
def func_12306(name):
    info = w_r_info("r")
    print("%s查看了余票,还剩%s张"%(name,info["count"]))
    if info["count"] > 0 :
        info["count"] -= 1
        time.sleep(1)
        w_r_info("w",info)
        print("%s买到了票" % (name))
        
    else:print("%s没有买到票" % (name))
if __name__ == '__main__':
    lst = ["赵一","钱二","sun3"]
    for i in lst:
        p = Process(target=func_12306,args=(i,))
        p.start()

  

1.2.1 Lock

# 一把锁 Lock 改造(模拟12306)
import json,time
from multiprocessing import Process,Lock
​
def w_r_info(mod,dic=None):
    if mod == "r":
        with open("12306",mode=mod,encoding='utf-8') as f:
            dic = json.load(f)
            return dic
    else:
        with open("12306",mode=mod,encoding='utf-8') as f:
            json.dump(dic,f)
​
def func_12306(name,lock):
​
    info = w_r_info("r")                            # 读余票
    print("%s查看了余票,还剩%s张"%(name,info["count"]))
    time.sleep(0.5)
    lock.acquire()                                  # 上锁,后面人进来就是修改过的值
    info = w_r_info("r")                            # 读余票
    if info["count"] > 0 :
        info["count"] -= 1
        w_r_info("w",info)
        print("%s买到了票" % (name))
        # lock.release()
    else:print("%s没有买到票" % (name))
    lock.release()                                   # 解锁
if __name__ == '__main__':
    lock = Lock()                                    # 创建一把锁
    lst = ["赵一","钱二","sun3"]
    for i in lst:
        p = Process(target=func_12306,args=(i,lock))
        p.start()

  

1.2.2 Semaphore

# 多把锁
import time
from multiprocessing import Process,Semaphore
def func(n,sem):
    with sem:
        time.sleep(1)
        print("%s在拉屎"%(n))
​
if __name__ == '__main__':
    lst = ["赵一","钱二","孙三","李四","周五","吴六","郑七","王八"]
    sem = Semaphore(1)                  # 相当于指定多少个人同时拉屎
    for i in lst:
        p = Process(target=func,args=(i,sem))
        p.start()

  

1.3 Event 进程事件

"""
# 阻塞事件 :
    e = Event()生成事件对象e   
    e.wait()动态给程序加阻塞 , 程序当中是否加阻塞完全取决于该对象中的is_set() [默认返回值是False]
    # 如果是True  不加阻塞
    # 如果是False 加阻塞
​
# 控制这个属性的值
    # set()方法     将这个属性的值改成True
    # clear()方法   将这个属性的值改成False
    # is_set()方法  判断当前的属性是否为True  (默认上来是False)
"""
​
# 红绿灯解释进程事件
import time,random
from multiprocessing import Process,Event
​
def light(e):
    print("红灯!")
    while True:
        if e.is_set():       # 1,is_set() 默认值是 False  阻塞
            time.sleep(2)    # 4,等待绿灯2秒,变为红灯
            print("红灯!")
            e.clear()        # 5,设置is_set()为 False  阻塞
        else:
            time.sleep(2)    # 2,等待红灯2秒
            print("绿灯!")
            e.set()          # 3,设置is_set()为True   ,放行
​
def car(e,i):
    # 格式一定,因为当红灯时,小车进来等待红灯,wait阻塞,当绿灯时继续执行代码,如果反过来,红灯等待,阻塞,当绿灯时,这个程序已经结束了,同行不了
    if not e.is_set():
        time.sleep(random.uniform(0.3,0.8))         
        print(i,"等待红灯中")
        e.wait()
    print(i,"通行了")
​
if __name__ == '__main__':
    e = Event()
    lst= []
    p1 = Process(target=light, args=(e,))
    p1.daemon = True
    p1.start()
​
    for i in range(10):
        p2 = Process(target=car,args=(e,i))
        time.sleep(1)
        p2.start()
        lst.append(p2)
    for i in lst:
        p2.join()
    print("主程序结束")

  

1.4 Queue 进程队列

# 进程之间的通信 IPC (inter process communication)
from multiprocessing import Queue
q = Queue()             # 创建一个队列对象,可以设置队列长度
q.put(值)               # 往队列对象里放值
q.get()                 # 从队列对象里取值
"""
队列特点: 先进先出,后进后出
1,如果超过了队列的指定长度,在继续存值会出现阻塞
2,队列中如果已经没有数据了,在调用get会发生阻塞
"""
q.put_nowait()          # 非阻塞版本的put,超出长度后,直接报错
q.get_nowait()          # 如果没有值会报错,windows好使,可以使用try抑制报错

  

1.4.1 进程之间数据共享

from multiprocessing import Process,Queue
def func(p):
    p.put("我爱你")                     # 子进程放值
​
if __name__ == '__main__':
    p = Queue()
    pro = Process(target=func,args=(p,))
    pro.start()
    pro.join()
    print(p.get())                      # 主进程取值

  

1.4.1.1 Manager 字典,列表共享数据

from multiprocessing import Manager,Process
def func(dic):
    print(dic)
​
if __name__ == '__main__':
    m = Manager()
    dic = m.dict({"name":"bajie"})
    Process(target=func,args=(dic,)).start()
    time.sleep(1)            # 共享数据必须慢一点
    print(dic,'--------')

  

1.4.2 生产者与消费者模型

# 消费者模型
def consumer(q,name):
    while True:
        food = q.get()
        if food is None:
            break
        time.sleep(random.uniform(0.1,1))
        print("%s 吃了一个%s" % (name,food))
    
# 生产者模型
def producer(q,name,food):
    for i in range(5):
        time.sleep(random.uniform(0.1,1))
        # 打印生产的数据
        print("%s 生产了 %s%s" % (name,food,i))
        # 存储生产的数据
        q.put(food + str(i))
​
if __name__ == "__main__":
    q = Queue()
    p1 = Process(target=consumer,args=(q,"宋云杰"))
    p2 = Process(target=producer,args=(q,"马生平","黄瓜"))
    p1.start()
    p2.start()
    # 在生产者生产完所有数据之后,在队列的末尾添加一个None
    p2.join()
    q.put(None)

  

1.4.3 生产者与消费者模型升级版 (JoinableQueue)

from multiprocessing import Process, JoinableQueue
import time,random
"""
put 存储
get 获取
task_done 
join
​
task_done 和 join 配合使用的
队列中 1 2 3 4 5
put 一次 内部的队列计数器加1
get 一次 通过task_done让队列计数器减1
join函数,会根据队列计数器来判断是阻塞还是放行
    队列计数器  = 0 , 意味着放行
    队列计数器 != 0 , 意味着阻塞
"""
​
# 1.基本语法
"""
jq =JoinableQueue()
jq.put("a")
print(jq.get())
# 通过task_done让队列计数器减1
jq.task_done()
jq.join()
print("finish")
"""
​
# 2.改造生产者和消费者模型
def consumer(q,name):
    while True:
        food = q.get()
        time.sleep(random.uniform(0.1,1))
        print("%s 吃了一个%s" % (name,food))
        # 当队列计数器减到0的时,意味着进程队列中的数据消费完毕
        q.task_done()
    
# 生产者模型
def producer(q,name,food):
    for i in range(5):
        time.sleep(random.uniform(0.1,1))
        # 打印生产的数据
        print("%s 生产了 %s%s" % (name,food,i))
        # 存储生产的数据
        q.put(food + str(i))
    
​
if __name__ == "__main__":
    q =JoinableQueue()
    # 消费者
    p1 = Process(target=consumer,args=(q,"宋云杰"))
    # 生产者
    p2 = Process(target=producer,args=(q,"马生平","黄瓜"))
    
    # 设置p1消费者为守护进程
    p1.daemon = True
    p1.start()
    p2.start()
    
    # 把所有生产者生产的数据存放到进程队列中
    p2.join()
    # 为了保证消费者能够消费完所有数据,加上队列.join
    # 当队列计数器减到0的时,放行,不在阻塞,程序彻底结束.
    q.join()
    print("程序结束 ... ")

  

2, 线程的基本使用

"""
cpu执行程序的最小单位.
进程与线程的关系:  1,进程是资源,线程是工人    2,一个进程至少有一个线程
"""

  

2.1 创建多线程

from threading import Thread,currentThread
​
currentThread().ident           # 获取线程号
def func():
    print("当前线程ID:%s"%(currentThread().ident))
    
if __name__ == '__main__':
    t = Thread(target=func).start()
    print("当前线程ID:%s" % (currentThread().ident))

  

2.1.1 进程与线程谁的速度快?

# 测试线程速度    1000个大概0.5秒
import time
from threading import Thread,currentThread
​
def func():
    print("当前线程ID:%s"%(currentThread().ident))
​
if __name__ == '__main__':
    lst = []
    a = time.time()
    for i in range(1000):
        t = Thread(target=func)
        t.start()
        lst.append(t)
    for i in lst:
        i.join()
    b = time.time()
    print("用了{}秒".format(b-a))
    
# 测试进程速度    1000个大概5秒
from multiprocessing import Process
​
def func():
    print("当前线程ID:%s"%(currentThread().ident))
​
if __name__ == '__main__':
    lst = []
    a = time.time()
    for i in range(1000):
        t = Process(target=func)
        t.start()
        lst.append(t)
    for i in lst:
        i.join()
    b = time.time()
    print("用了{}秒".format(b-a))

  

2.1.2 线程共享进程资源

from threading import Thread
​
num = 520
def func():
    global num
    num -= 1
​
if __name__ == '__main__':
    t = Thread(target=func)
    t.start()
    t.join()
    
    print(num)           # 519

  

2.1.3 线程的相关函数

import time
from threading import Thread,currentThread,enumerate
from multiprocessing import Process
​
def func():
    time.sleep(1)
​
if __name__ == '__main__':
    t = Thread(target=func)
    t.start()
    print(t.is_alive())                  # 获取线程存活状态
    t.setName("爬树据")                   # 设置线程名字
    print(t.getName())                   # 获取线程名字
    print(currentThread().ident)         # 获取线程ID
    print(enumerate())                   # 获取存活线程的列表

  

2.2 守护线程

# ### 守护线程 : 等待所有线程全部执行完毕之后,自己在终止,守护所有线程
​
from threading import Thread
import time
def func1():
    while True:
        time.sleep(0.5)
        print("我是func1")
​
def func2():
    print("我是func2 start ... ")
    time.sleep(3)
    print("我是func2 end ... ")
​
def func3():
    print("我是func3 start ... ")
    time.sleep(5)
    print("我是func3 end ... ")
​
if __name__ == "__main__":
    t1 = Thread(target=func1)
    t2 = Thread(target=func2)
    t3 = Thread(target=func3)
    
    # 在start调用之前,设置线程为守护线程
    t1.setDaemon(True)
    
    t1.start()
    t2.start()
    t3.start()
    
    print("主线程执行结束 .... ")

  

2.3 线程中的数据安全

2.3.1 Lock锁

# 一把锁
from threading import Thread,Lock
​
n = 0
def add(lock):
    global n
    with lock:
        for i in range(1000000):
            n += 1
def rem(lock):
    global n
    with lock:
        for i in range(1000000):
            n -= 1
if __name__ == "__main__":
    lst = []
    lock = Lock()
    t1 = Thread(target = add,args=(lock,))
    t2 = Thread(target = rem,args=(lock,))
    t1.start()
    t2.start()
    lst.append(t1)
    lst.append(t2)
    for i in lst:
        i.join()
    print(n)

  

2.3.2 Semaphore 信号量

# 信号量 Semaphore
"""
再创建线程的时候是异步创建
在执行任务时,遇到Semaphore进行上锁,会变成同步程序
"""
from threading import Semaphore , Thread
import time
​
def func(i,sm):
    with sm:
        print(i)
        time.sleep(3)   
if __name__ == "__main__":
    # 支持同一时间,5个线程上锁
    sm = Semaphore(5)
    for i in range(20):
        Thread(target=func,args=(i,sm)).start()

  

2.3.3 递归锁 Rlock

# 语法死锁
from threading import Lock
lock = Lock()
lock.acquire()
lock.acquire()
lock.acquire()
​
lock.release()
​
# 逻辑死锁
多把锁,
​
# 递归锁
# (3) 递归锁的使用
"""
递归锁专门用来解决这种死锁现象,临时用于快速解决线上项目发生阻塞死锁问题的
"""
rlock = RLock()
rlock.acquire()
rlock.acquire()
rlock.acquire()
rlock.acquire()
print(112233)
rlock.release()
rlock.release()
rlock.release()
rlock.release()
​
print("程序结束 ... ")
# 依然可以执行

  

2.4 Event 事件

from threading import Thread
​
# 模拟链接远程数据库
def check(e):
    # 用一些延迟来模拟检测的过程
    time.sleep(random.randrange(1,6)) # 1 2 3 4 5
    # time.sleep(1)
    print("开始检测链接用户的合法性")
    e.set()
    
def connect(e):
    sign = False
    for i in range(1,4): # 1 2 3    
        e.wait(1)   
        if e.is_set():
            print("数据库链接成功 ... ")
            sign = True
            break
        else:
            print("尝试链接数据库第%s次失败 ... " % (i))
            
    if sign == False:
        raise TimeoutError
        
e = Event()
Thread(target=connect,args=(e,)).start()
Thread(target=check,args=(e,)).start()

  

2.5 线程队列 PriorityQueue

from queue import Queue
# Queue (先进先出,后进后出)
​
from queue import LifoQueue
# LifoQueue(先进后出,后进先出)
​
方法与进程队列相同
put()
get()
put_nowait()
get_nowait()
​
from queue import PriorithQueue
# 优先级队列
"""
1,按照ascii码排序输出, 2,不可以存放不同的数据类型
"""

  

3, 池 Pool

3.1 进程池 & 线程池

# 引入模块
from concurrent.futures import ProcessPoolExecutor       # 进程池
from concurrent.futures import ThreadPoolExecutor        # 线程池
from threading import current_thread
print(current_thread().ident)                            # 线程号
import os
print(os.getpid())                                       # 进程号
print(os.cpu_count())                                    # cpu逻辑数
​
# 进程池的基本使用
def func(i):
    print(i,"start...进程号:",os.getpid())
    time.sleep(0.1)
    print(i,"end.....进程号:",os.getpid())
    return os.getpid()
​
if __name__ == '__main__':
    setvar = set()
    lst = []
    # 创建进程池,默认为cpu逻辑数,
    p = ProcessPoolExecutor(30)
    # 异步提交任务  ---->  p.submit(func,i)
    for i in range(30):
        # 可以拿到返回值,是一个对象
        res = p.submit(func,i)
        lst.append(res)
    for i in lst:
        # result() 获取返回值,同步阻塞
        setvar.add(i.result())
    print(setvar,len(setvar))
​
    
# 线程池的基本使用
def func(i):
    print(i,"start...线程号:",current_thread().ident)
    time.sleep(1)
    print(i,"end.....进程号:",os.getpid())
    # time.sleep(1)
    print(i, "end.....进程号:%s,线程号:%s"%(os.getpid(),current_thread().ident) )
    return current_thread().ident
​
if __name__ == '__main__':
    setvar = set()
    lst = []
    # 创建进程池,默认为cpu逻辑数,
    p = ThreadPoolExecutor(60)
    # 异步提交任务  ---->  p.submit(func,i)
    for i in range(60):
        # 可以拿到返回值,是一个对象
        res = p.submit(func,i)
        lst.append(res)
    for i in lst:
        # result() 获取返回值,同步阻塞
        setvar.add(i.result())
    print(setvar,len(setvar))
   

  

3.1.1 线程池 map 的使用

def func(i):
    time.sleep(5)
    print(i, "end.....进程号:%s,线程号:%s"%(os.getpid(),current_thread().ident) )
    return current_thread().ident
​
if __name__ == '__main__':
    setvar = set()
    lst = []
    # 创建线程池,(最大允许并发5个线程)
    p = ThreadPoolExecutor(5)
    it = p.map(func,range(20))
    p.shutdown()
​
    for i in lst:
        print(i)

  

3.1.2 回调函数

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread  as cthread
import os,time
​
# 返回值对象.add_done_callback(回调函数)
​
def func1(i):
    print("process start ... " , os.getpid())
    time.sleep(1)
    print("process end ... ", i)
    return "*" * i
​
def func2(i):
    print("thread start ... " , cthread().ident)
    time.sleep(1)
    print("thread end ... ", i)
    return "*" * i
​
def call_back1(obj):
    print("<===回调函数callback进程号===>" , os.getpid())
    print(obj.result())
    
def call_back2(obj):
    print("<===回调函数callback线程号===>" ,cthread().ident)
    print(obj.result())
​
# (1) 进程池的回调函数: 由主进程执行调用完成的
​
if __name__ == "__main__":
    p = ProcessPoolExecutor()
    for i in range(1,11):
        res = p.submit(func1,i)
        # print(res.result())
        res.add_done_callback(call_back1)
        # self.func(func2)
    p.shutdown()
    print("主进程执行结束 ... " , os.getpid())
​
​
# (2) 线程池的回调函数 : 由当前子线程调用完成的
if __name__ == "__main__":
    tp = ThreadPoolExecutor(5)
    for i in range(1,11):
        res = tp.submit(func2,i)
        res.add_done_callback(call_back2)
        
    tp.shutdown()
    print("主线程执行结束 ... " , cthread().ident)

  

 

4, 协程

# 能够在一个线程中多个任务(函数)之间来回切换,那么每个任务都是协程
​
# 线程与协程的区别:
    线程:操作系统切换,开销大,操作系统不可控,让操作系统的压力的大,操作系统对IO(细微的阻塞)操作更加敏感
    协程:prthon代码切换,开销小,用户可控,不会增加操作系统的压力。在用户层面,对细微的阻塞的感知相对较低(open,print)

  

4.1 协程基本操作

import time,gevent
def func():
    print("1")
    time.sleep(2)               # 操作系统阻塞,协程不识别,不会切换任务
    print("2")
​
g = gevent.spawn(func)           # 创建协程任务
gevent.sleep(3)                  # 协程阻塞
print(3)
​
# 识别所有阻塞,底层覆盖
from gevent import monkey
monkey.patch_all()
​
# 阻塞所有任务
g.joinall(list(协程对象))
​
# 查看g协程任务返回值
g.value
 

  

posted @ 2020-08-23 19:02  bajie_new  阅读(113)  评论(0编辑  收藏  举报