python 并发

并发概念

# 进程 : 进行中的程序就是一个进程
    占用资源 需要操作系统调度
    pid : 能够唯一标识一个进程
    计算机中最小的资源分配单位
# 并发
    多个程序同时执行 : 只有一个cpu,多个程序轮流在一个cpu上执行
    宏观上 : 多个程序在同时执行
    微观上 : 多个程序轮流在一个cpu上执行 本质上还是串行
# 并行
    多个程序同时执行,并且同时在多个cpu上执行
# 同步
    在做A事的时候发起B件事,必须等待B件事结束之后才能继续做A事件
# 异步
    在做A事的时候发起B时间,不需要等待B事件结束就可以继续A事件
# 阻塞
    如果CPU不工作  input accept recv recvfrom sleep connect
# 非阻塞
    CPU在工作
# 线程
    线程是进程中的一个单位,不能脱离进程存在
    线程是计算机中能够被CPU调度的最小单位

进程线程协程的区别

 

进程
线程
    正常的开发语言 多线程可以利用多核
    cpython解释器下的多个线程不能利用多核 : 规避了所有io操作的单线程
协程
    是操作系统不可见的
    协程本质就是一条线程 多个任务在一条线程上来回切换
    利用协程这个概念实现的内容 : 来规避IO操作,就达到了我们将一条线程中的io操作降到最低的目的
# 进程 数据隔离 数据安全  操作系统级别  开销非常大  能利用多核
# 线程 数据共享 数据不安全  操作系统级别  开销小      不能利用多核   一些和文件操作相关的io只有操作系统能感知到
# 协程 数据共享 数据不安全    用户级别      更小        不能利用多核   协程的所有的切换都基于用户,只有在用户级别能够感知到的io才会用协程模块做切换来规避(socket,请求网页的)

 

 

I/O操作

I/O操作 相对内存来说
输入Input输出Output
输入是怎么输入 :键盘\input\read\recv
输出是怎么输出 :显示器 打印机 播放音乐\print\write\send
文件操作 :read write
网络操作 :send recv recvfrom
函数     :print input
View Code

计算机的工作分为两个状态:

计算机的工作分为两个状态
    CPU工作   : 做计算(对内存中的数据进行操作)的时候工作
    CPU不工作 : IO操作的时候
CPU的工作效率 500000条指令/ms
View Code

多道操作系统

多道操作系统 :一个程序遇到IO就把CPU让给别人
    顺序的一个一个执行的思路变成
    共同存在在一台计算机中,其中一个程序执行让出cpu之后,另一个程序能继续使用cpu
    来提高cpu的利用率
    单纯的切换会不会占用时间 : 会
    但是多道操作系统的原理整体上还是节省了时间,提高了CPU的利用率
    时空复用的概念
View Code

分时操作系统

单cpu 分时操作系统 : 把时间分成很小很小的段,每一个时间都是一个时间片,
每一个程序轮流执行一个时间片的时间,自己的时间片到了就轮到下一个程序执行 -- 时间片的轮转
    老教授 24h全是计算 没有io
        先来先服务 FCFS
    研究生 5min全是计算 没有io
        短作业优先
    研究生2 5min全是计算 没有io
    没有提高CPU的利用率 \ 提高了用户体验
View Code

 

 

进程

串行: 所有的进程由cpu一个一个的解决.

并发:单个cpu,同时执行多个进程(来回切换的),看起来像是同时运行.

并行:多个cpu,真正的同时运行多个进程.

阻塞:遇到IO才叫阻塞.

一个cpu运行两个进程,其中一个进程完全没有阻塞,

非阻塞: 没有IO.

进程的创建:

什么是开启多个进程:socket:server,client两个进程

python中,如果一次想开启多个进程,必须是一个主进程,开启多个子进程

linux,windows:有主进程开启子进程

相同点:主进程开启子进程,两个进程都有相互隔离的独立空间,互不影响

不同点:

  linux:子进程空间的初始数据完全是从主(父)进程copy一份

  windows:子进程空间初始数据完全是从主(父)进程copy一份,但是有所不同

进程的三状态图

就绪 运行 阻塞

# 就绪 -操作系统调度->运行 -遇到io操作-> 阻塞 -阻塞状态结束-> 就绪
           -时间片到了-> 就绪

 

multiprocess模块多进程

multiple 多元化的

processing 进程

multiprocessing 多元的处理进程的模块

 

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。
    multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。

  multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

    需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

 

 

Process类

创建进程的类

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

 

参数介绍:

group参数未使用,值始终为None
 
target表示调用对象,即子进程要执行的任务

args表示调用对象的位置参数元组,args=(1,2,'egon',)

kwargs表示调用对象的字典,kwargs{'name':'egon','age':18}

name为子进程的名称

 

 进程方法

p.start():启动进程,并调用该子进程中的p.run() 

p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

 

属性介绍:

p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
 
p.name:进程的名称

p.pid:进程的pid

p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)

p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

 

注意:在windows中Process()必须放到# if __name__ == '__main__':下

创建开启子进程的两种方式

函数方法

from multiprocessing import Process
import time
def task(name):
    print('%s is runing' %(name))
    time.sleep(3)
    print('%s is done' % (name))


if __name__ == '__main__':
    p = Process(target=task,args=('壮壮',))
    # p = Process(target=task,kwargs={'name':'壮壮'})  两种传参方式
    p.start()
    print('====主')

类方法

from multiprocessing import Process
import time
# 方式二:

class MyProcess(Process):
    def __init__(self,name):
        
        self.name = name
        super().__init__()
        
    def run(self):  # 必须定义一个run方法
        print('%s is runing' % (self.name))
        time.sleep(3)
        print('%s is done' % (self.name))
    
    
if __name__ == '__main__':
    p = MyProcess('小明')
    p.start()
    print('===主')

 

多进程之间的数据隔离

多进程之间的数据隔离
from multiprocessing import Process
n = 0
def func():
    global n
    n += 1

if __name__ == '__main__':
    p_l = []
    for i in range(100):
        p = Process(target=func)
        p.start()
        p_l.append(p)
    for p in p_l:p.join()
    print(n)

 

join方法:

join 主进程等待子进程结束之后,在执行

join开启一个进程:

from multiprocessing import Process
import time

def task(name):
    time.sleep(1)
    print(f"{name}is running")

if __name__ == '__main__':
     p = Process(target=task,args=("海洋",))
     p.start()
     p.join()           #告知主进程,p进程结束之后,主进程在结束,join有些阻塞的意思
     print("___主进程")

#      p1.start()
#      p2.start()       #p1,p2,p3三个子进程先后运行顺序不定,start只是通知一下操作系统
#      p3.start()       #操作系统调用cpu先运行谁,谁先执行

join串行:

from multiprocessing import Process
import time

def task(name,sec):
    time.sleep(sec)
    print(f"{name}is running")

if __name__ == '__main__':
     p1 = Process(target=task, args=("小明",1))
     p2 = Process(target=task, args=("明明",2))
     p3 = Process(target=task ,args=("大明",3))
     start_time = time.time()

     p1.start()
     p1.join()
     p2.start()
     p2.join()
     p3.start()
     p3.join()

     print(f"主进程{time.time() - start_time}")

join并发:

from multiprocessing import Process
import time

def task(sec):
    time.sleep(sec)
    print(f"is running")

if __name__ == '__main__':
     start_time = time.time()
     list = []

     for i in range(1,4):
          p = Process(target=task, args=(i,))
          p.start()
          list.append(p)

     for i in list:
          i.join()

     print(f"主进程{time.time() - start_time}")

进程对象的其他属性:

属性:

from multiprocessing import Process
import time

def task(name):
    print(f"{name}is running")
    time.sleep(3)
    print(f"{name}is done")

if __name__ == '__main__':
     p = Process(target=task,args=("小明",),name="大明")  #name给进程对象设置name属性
     p.start()
     # print(p.pid)         #获取到进程号

     time.sleep(1)          #睡一秒,子进程已经执行完成
     p.terminate()          #强制结束子进程,强制执行也会有执行时间
                            #terminate跟start一样工作原理,都要通知操作系统开启子进程
                            #内存终止或者开启都要需要时间的

     time.sleep(1)          #睡一秒,让terminate杀死
     print(p.is_alive())    #判断子进程是否存活,只是查看内存中p子进程是否还运行
     print("主进程")

socket server的例子

server

import socket
from multiprocessing import Process
def talk(conn):
    while True:
        msg = conn.recv(1024).decode('utf-8')
        ret = msg.upper().encode('utf-8')
        conn.send(ret)
    conn.close()

if __name__ == '__main__':
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.listen()
    while True:
        conn, addr = sk.accept()
        Process(target = talk,args=(conn,)).start()
    sk.close()
View Code

client

import time
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',9001))

while True:
    sk.send(b'hello')
    msg =sk.recv(1024).decode('utf-8')
    print(msg)
    time.sleep(0.5)

sk.close()
View Code

 

 

僵尸进程:

init是所有进程的父进程:

僵尸进程,僵尸是什么,死而没有消失

主进程创建多个短暂周期的子进程,当子进程退出,是需要等待父进程处理,而父进程没有及时对子进程回收,那么子进程的进程符仍然保存在系统中,这种进程就是僵死进程

什么进程描述符:每一个进程都有描述符,io请求,数据指针

from multiprocessing import Process
import time
import os

def task(name):
    print(f"{name}is running")
    print(f"子进程开始了:{os.getpid()}")
    time.sleep(50)


if __name__ == '__main__':
    for i in range(100):
        p = Process(target=task, args=("海洋",))
        p.start()
        print(f"___主进程:{os.getpid()}")
View Code

孤儿进程:

孤儿进程:孤儿进程是因为主进程的退出,他下面的所有子进程都变成孤儿进程了,init会对孤儿进行回收,释        放掉占用系统的资源,这种回收也是为了节省内存。

孤儿进程无害,如果僵尸进程挂了,init会对孤儿进程回收,init是所有进程的祖进程,linux中为1,0系统
View Code

 

守护进程:

将一个子进程设置成守护进程,当父进程结束,子进程一定会结束,避免孤儿进程产生,应为回收机制

父进程不能创建子进程

函数方法:

#守护进程会在主进程代码执行结束后终止,守护进程内无法在开启子进程

from multiprocessing import Process
import time
import os

def task(name):
    print(f"{name}is running")
    print(f"子进程开始了:{os.getpid()}")
    time.sleep(50)

if __name__ == '__main__':
     p = Process(target=task,args=("海洋",))
     p.daemon = True  #将p子进程设置成守护进程,守护子进程,只要主进程结束
                      #子进程无论执行与否都马上结束,daemon,开启在start上面
     p.start()
     print(f"___主进程:{os.getpid()}")

类方法:

from multiprocessing import Process
开启进程的另一种方式
class 类名(Process):
    def __init__(self,参数):
        self.属性名 = 参数
        super().__init__()
    def run(self):
        print('子进程要执行的代码')
p = 类名()
p.start()

# 守护进程 : 会等待主进程代码结束之后就立即结束
p = 类名()
p.daemon = True   # 设置守护进程
p.start()
# 一般情况下,多个进程的执行顺序,可能是:
    # 主进程代码结束--> 守护进程结束-->子进程结束-->主进程结束
    # 子进程结束 -->主进程代码结束-->守护进程结束-->主进程结束
View Code

进程之间通信(IPC):(重点)

第一种:基于文件+锁的形式:效率低,麻烦
第二种:基于队列,推荐的使用形式
第三种:基于管道,管道自己加锁,底层可能会出现数据丢失损坏,队列和管道都是将数据存放于内存中

 

进程锁(互斥锁)

互斥锁保证了每次只有一个线程进行写入操作,只有当这个线程解锁,在运行其他资源,上锁和解锁都需要自己添加

 

两种方式

from multiprocessing import Lock

第一种: lock
= Lock() lock.acquire() print(1) lock.release() 第二种 with lock: buy_ticket(i)

 

 

#上锁:
#一定要是同一把锁:只能按照这个规律,上锁一次,解锁一次

#互斥锁与join区别:
#共同点:都是完成了进程之间的串行
#区别:join认为控制进程的串行,互斥锁是解决抢占的资源,保证公平性

from multiprocessing import Process
from multiprocessing import Lock
import time
import os
import random

def task1(lock):
    print("test1")                     #验证CPU遇到IO切换
    lock.acquire()
    print("task1 开始打印")
    time.sleep(random.randint(1,3))
    print("task1 打印完成")
    lock.release()

def task2(lock):
    print("test2")
    lock.acquire()                      #上锁
    print("task2 开始打印")
    time.sleep(random.randint(1,3))#阻塞,cpu切换任务,别的任务都在锁,回来继续执行这个程序
    print("task2 打印完成")
    lock.release()                      #解锁

def task3(lock):
    print("test2")
    lock.acquire()
    # lock.acquire()                    #死锁错误示例
    print("task3 开始打印")
    time.sleep(random.randint(1,3))
    print("task3 打印完成")
    lock.release()

if __name__ == '__main__':
     lock = Lock()                              #一把锁

     p1 = Process(target=task1,args=(lock,))    #三个进程哪个先到先执行
     p2 = Process(target=task2,args=(lock,))
     p3 = Process(target=task3,args=(lock,))

     p1.start()
     p2.start()
     p3.start()

 

 

互斥锁买票示例:

import json
import time
from multiprocessing import Process,Lock

def search(i):
    with open('ticket',encoding='utf-8') as f:
        ticket = json.load(f)
    print('%s :当前的余票是%s张'%(i,ticket['count']))

def buy_ticket(i):
    with open('ticket',encoding='utf-8') as f:
        ticket = json.load(f)
    if ticket['count']>0:
        ticket['count'] -= 1
        print('%s买到票了'%i)
    time.sleep(0.1)
    with open('ticket', mode='w',encoding='utf-8') as f:
        json.dump(ticket,f)

def get_ticket(i,lock):
    search(i)
    with lock:   # 代替acquire和release 并且在此基础上做一些异常处理,保证即便一个进程的代码出错退出了,也会归还钥匙
        buy_ticket(i)


if __name__ == '__main__':
    lock = Lock()     # 互斥锁
    for i in range(10):
        Process(target=get_ticket,args=(i,lock)).start()
View Code

 

队列:

1. 进程之间的通信最好的方式是基于队列

2. 队列是实现进程之间通信的工具,存在内存中的一个容器,最大的特点是符合先进先出的原则

from multiprocessing import Queue,Process
def pro(q):
    for i in range(10):
        print(q.get())
def son(q):
    for i in range(10):
        q.put('hello%s'%i)

if __name__ == '__main__':
    q = Queue()
    p = Process(target=son,args=(q,))
    p.start()
    p = Process(target=pro, args=(q,))
    p.start()

 

队列模式:

  多个进程抢占一个资源:串行,有序以及数据安全,买票

  多个进程实现并发的效果:生产者消费模型

import time
import random
from multiprocessing import Queue,Process

def consumer(q,name): # 消费者:通常取到数据之后还要进行某些操作
    while True:
        food = q.get()
        if food:
            print('%s吃了%s'%(name,food))
        else:break

def producer(q,name,food): # 生产者:通常在放数据之前需要先通过某些代码来获取数据
    for i in range(10):
        foodi = '%s%s'%(food,i)
        print('%s生产了%s'%(name,foodi))
        time.sleep(random.random())
        q.put(foodi)

if __name__ == '__main__':
    q = Queue()
    c1 = Process(target=consumer,args=(q,'alex'))
    c2 = Process(target=consumer,args=(q,'alex'))
    p1 = Process(target=producer,args=(q,'大壮','泔水'))
    p2 = Process(target=producer,args=(q,'b哥','香蕉'))
    c1.start()
    c2.start()
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    q.put(None)
    q.put(None)

生产者消费者网页(爬取网页) 

import requests
from multiprocessing import Process,Queue
url_dic = {
    'cnblogs':'https://www.cnblogs.com/Eva-J/articles/8253549.html',
    'douban':'https://www.douban.com/doulist/1596699/',
    'baidu':'https://www.baidu.com',
    'gitee':'https://gitee.com/old_boy_python_stack__22/teaching_plan/issues/IXSRZ',
}

def producer(name,url,q):
    ret = requests.get(url)
    q.put((name,ret.text))

def consumer(q):
    while True:
        tup = q.get()
        if tup is None:break
        with open('%s.html'%tup[0],encoding='utf-8',mode='w') as f:
            f.write(tup[1])

if __name__ == '__main__':
    q = Queue()
    pl = []
    for key in url_dic:
        p = Process(target=producer,args=(key,url_dic[key],q))
        p.start()
        pl.append(p)
    Process(target=consumer,args=(q,)).start()
    for p in pl:p.join()
    q.put(None)

    # join n 个进程   n个进程必须都执行完才继续
    # for i in range(4):
    #     print(q.get())
View Code

数据共享Manager类

# Manager dict list 只要是共享的数据都存在数据不安全的现象
# 需要我们自己加锁来解决数据安全问题
from multiprocessing import Process,Manager,Lock

def change_dic(dic,lock):
    with lock:
        dic['count'] -= 1

if __name__ == '__main__':
    # m = Manager()
    with Manager() as m:
        lock = Lock()
        dic = m.dict({'count': 100})
        # dic = {'count': 100}
        p_l = []
        for i in  range(100):
            p = Process(target=change_dic,args=(dic,lock))
            p.start()
            p_l.append(p)
        for p in p_l : p.join()
        print(dic)
View Code

 

 

线程

进程:进程是分配资源的基本单位,内存中开辟空间,为线程提供资源,一个程序可以开启多个进程

线程:CPU调度的最小单位,执行单位,线程也被称作为轻量级的进程,动态的

  • 主线程是进程空间存活在内存中的一个必要条件

开启QQ:开启一个进程,在内存中开辟空间加载数据,启动一个线程执行代码

线程依赖进程的一个进程可以包含多个线程,但是一定有一个主线程,线程才是CPU执行的最小单元

进程线程对比:

  • 1,开启多进程开销非常大,10-100倍,而开启线程开销非常小

  • 2.开启多进程速度慢,开启多线程速度快

  • 3.进程之间数据不共享,线程共享数据

全局解释器锁 GIL

在cpython解释器下 :GIL锁(全局解释器锁) 导致了同一个进程中的多个线程不能利用多核

多线程应用场景:

并发:一个CPU可以来回切换(线程之间切换),多进程并发,多线程的并发

多进程并发:开启多个进程,并发的执行

多线程并发:开启线程,并发的执行

如果遇到并发:多线程居多

开启线程的两种方式:

  线程绝对要比进程开启速度快

函数开启:
#先打印小明,线程要比进程速度快,如果是进程先打印主线程
from threading import Thread

def task(name):
print(f'{name} is running')

if __name__ == '__main__':
t = Thread(target=task,args=("小明",))
t.start()
print("主线程")

#子进程睡眠3秒,先运行主进程
from threading import Thread
import time
x = 1000

def task():
time.sleep(3)
print('子线程....')

def main():
print('111')
print('222')
print('333')

if __name__ == '__main__':
t = Thread(target=task)
t.start()
main()

# 结果是111
222
333
子线程....

 

面向对象的方式起线程

from threading import Thread

class MyThread(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        print(f'{self.name} is running')

if __name__ == '__main__':
    t = MyThread("小明")
    t.start()
    print("主线程")

线程之间数据共享

from threading import Thread
x = 1000
def task():
    global x
    x = 0

if __name__ == '__main__':
    t = Thread(target=task, )
    t.start()
    t.join()  # 告知主线程,等待子线程运行完毕在执行
    print(f'主线程:{x}')

线程的方法:

from threading import Thread
import threading
import time

def task(name):
    time.sleep(1)
    print(f'{name} is running')

if __name__ == '__main__':
    for i in range(5):
        t = Thread(target=task,args=("海洋",))
        t.start()              #线程对象的方法
    # print(t.is_alive())     #判断线程是否存活

    #threading模块的方法
    print(threading.current_thread().name)  #返回线程对象.name
    print(threading.enumerate())            #返回列表,返回的是所有线程对象
    print(threading.active_count())         #获取活跃的线程数量(包括主线程)
    print("主线程")
View Code

 

守护线程

守护线程必须等待主线程结束才结束,主线程必须等待所有的非守护线程结束才能结束,因为主线程的结束意味着进程的结束,这就是一个守护机制

多线程是同一个空间,同一个进程,进程代表,空间,资源,静态的:

import time
from threading import Thread

def son():

    while True:
        print('in son')
        time.sleep(1)

def son2():

    for i in range(3):
        print('in son2 ****')
        time.sleep(1)

# flag a   0s
t = Thread(target=son)
t.daemon = True
t.start()
Thread(target=son2).start()
# flag b
# 主线程会等待子线程结束之后才结束
# 主线程结束进程就会结束
# 守护线程随着主线程的结束而结束
# 守护线程会在主线程的代码结束之后继续守护其他子线程

 

守护进程和守护线程的结束原理不同

 

守护进程需要主进程来回收资源
守护线程是随着进程的结束才结束的
其他子线程-->主线程结束-->主进程结束-->整个进程中所有的资源都被回收-->守护线程也会被回收

进程是资源分配单位
子进程都需要它的父进程来回收资源
线程是进程中的资源
所有的线程都会随着进程的结束而被回收的

 

线程锁(互斥锁)

多个线程同时操作全局变量/静态变量 会产生数据不安全现象
互斥锁
+= -= 说明了线程之间数据的不安全
a = a.strip() 带返回值的都是先计算后赋值,数据不安全
a = a+1 /a+=1 数据不安全
if\while 数据不安全

append pop 说明了在线程中操作列表中的方法是数据安全的

 

from threading import Thread,Lock
import time
n = []
def append():
    for i in range(500000):
        n.append(1)
def pop(lock):
    for i in range(500000):
        with lock:
            if not n:
                time.sleep(0.0000001)    # 强制CPU轮转
            n.pop()

t_l = []
lock = Lock()
for i in range(20):
    t1 = Thread(target=append)
    t1.start()
    t2 = Thread(target=pop,args=(lock,))
    t2.start()
    t_l.append(t1)
    t_l.append(t2)
for t in t_l:
    t.join()
print(n)
# 不要操作全局变量,不要在类里操作静态变量
# += -= *= /= if while 数据不安全
# queue logging 数据安全的

 

单例模式(锁)(重点)

import time
class A:
    from threading import Lock
    __instance = None
    lock = Lock()
    def __new__(cls, *args, **kwargs):
        with cls.lock:
            if not cls.__instance:
                time.sleep(0.000001)   # cpu轮转
                cls.__instance = super().__new__(cls)
        return cls.__instance
View Code

互斥锁和递归锁

# Lock 互斥锁  效率高
# RLock 递归(recursion)锁 效率相对低
l = Lock()
l.acquire()
print('希望被锁住的代码')
l.release()

rl = RLock()  # 在同一个线程中可以被acquire多次
rl.acquire()
print('希望被锁住的代码')
rl.release()
View Code

递归锁

from threading import Thread,RLock as Lock

def func(i,lock):
    lock.acquire()
    lock.acquire()
    print(i,': start')
    lock.release()
    lock.release()
    print(i, ': end')

lock = Lock()
for i in range(5):
    Thread(target=func,args=(i,lock)).start()
View Code

死锁现象

死锁现象是怎么产生的?
    多把(互斥/递归)锁 并且在多个线程中 交叉使用
            fork_lock.acquire()
            noodle_lock.acquire()

            fork_lock.release()
            noodle_lock.release()
    如果是互斥锁,出现了死锁现象,最快速的解决方案把所有的互斥锁都改成一把递归锁
         程序的效率会降低的
    递归锁 效率低 但是解决死锁现象有奇效
    互斥锁 效率高 但是多把锁容易出现死锁现象

    一把互斥锁就够了
View Code

 

队列模式

线程之间数据安全的容器队列

先进先出 Queue

from queue import Empty  # 不是内置的错误类型,而是queue模块中的错误
q = queue.Queue(4)   # fifo 先进先出的队列
q.get()
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print('4 done')
q.put_nowait(5)
print('5 done')
try:
    q.get_nowait()
except Empty:pass
print('队列为空,继续其他内容')
# put_nowait: 不会等待队列有空闲位置再放入数据,如果数据放入不成功就直接崩溃
# get_nowait: 队列为空,取值的时候不等待,但是取不到值那么直接崩溃了
View Code

后进先出 LifoQueue

from queue import LifoQueue   
# last in first out 后进先出 栈
lq = LifoQueue()
lq.put(1)
lq.put(2)
lq.put(3)
print(lq.get())
print(lq.get())
print(lq.get())
View Code

优先级 PriorityQueue

from queue import PriorityQueue  # 优先级队列

priq = PriorityQueue()
priq.put((2,'alex'))
priq.put((1,'wusir'))
priq.put((0,'太白'))

print(priq.get())
print(priq.get())
print(priq.get())
View Code

池的定义

  要在程序开始的时候,还没提交任务先创建几个线程或者进程

  放在一个池子里,这就是池

为什么要用池

  如果先开好进程/线程,那么有任务之后就可以直接使用这个池中的数据了

  并且开好的线程或者进程会一直存在在池中,可以被多个任务反复利用
  这样极大的减少了开启\关闭\调度线程/进程的时间开销
  池中的线程/进程个数控制了操作系统需要调度的任务个数,控制池中的单位
  有利于提高操作系统的效率,减轻操作系统的负担
  发展过程

 

 

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(a,b):
    print('start')
    print(a,b)
    print('end')

if __name__ == '__main__':
    # p = ProcessPoolExecutor(max_workers=5)  #限制进程数量,默认为cpu个数
    p = ThreadPoolExecutor(4)                    #线程默认是CPU个数的五倍

    for i in range(4):
        p.submit(func,1,2)                      #给进程池放置任务启动,1,2为传参

 

同步:

任务发出去之后等待,直到这个任务最终结束之后,给我一个返回值,发布下一个任务

from concurrent.futures import ProcessPoolExecutor
import os
import time
import random

def task():
    print(f"{os.getpid()}is running")
    time.sleep(1)
    return f'{os.getpid()} is finish'

if __name__ == '__main__':
    p = ProcessPoolExecutor(4)

    for i in range(10):
        obj = p.submit(task,)
        print(obj.result())      #同步等待一个进程内容全部执行完成在执行下一个
View Code

 

异步:

将任务发给进程,不管任务如何,直接运行下一个

from concurrent.futures import ProcessPoolExecutor
import os
import time
import random

def task():
    print(f'{os.getpid()} is running')
    time.sleep(random.randint(0,2))
    return f'{os.getpid()} is finish'

if __name__ == '__main__':
    p = ProcessPoolExecutor(4)
    obj_l1 = []
    for i in range(10):
        obj = p.submit(task,)   # 异步发出.
        obj_l1.append(obj)

    # time.sleep(3)
    p.shutdown(wait=True)
    # 1. 阻止在向进程池投放新任务,
    # 2. wait = True 十个任务是10,一个任务完成了-1,直至为零.进行下一行.
    for i in obj_l1:
        print(i.result())

获取任务结果

import os
import time,random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(a,b):
    print(os.getpid(),'start',a,b)
    time.sleep(random.randint(1,4))
    print(os.getpid(),'end')
    return a*b

if __name__ == '__main__':
    tp = ProcessPoolExecutor(4)
    futrue_l = {}
    for i in range(20):         # 异步非阻塞的
        ret = tp.submit(func,i,b=i+1)
        futrue_l[i] = ret
        # print(ret.result())   # Future未来对象
    for key in futrue_l:       # 同步阻塞的
        print(key,futrue_l[key].result())
View Code

map

只适合传递简单的参数,并且必须是一个可迭代的类型作为参数

# map  只适合传递简单的参数,并且必须是一个可迭代的类型作为参数
import os
import time,random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(a):
    print(os.getpid(),'start',a[0],a[1])
    time.sleep(random.randint(1,4))
    print(os.getpid(),'end')
    return a[0]*a[1]

if __name__ == '__main__':
    tp = ProcessPoolExecutor(4)
    ret = tp.map(func,((i,i+1) for i in range(20)))
    for key in ret:       # 同步阻塞的
        print(key)
View Code

 

回调函数

add_done_callback()

# 回调函数 : 效率最高的
import time,random
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor

def func(a,b):
    print(current_thread().ident,'start',a,b)
    time.sleep(random.randint(1,4))
    print(current_thread().ident,'end',a)
    return (a,a*b)

def print_func(ret):       # 异步阻塞
    print(ret.result())

if __name__ == '__main__':
    tp = ThreadPoolExecutor(4)
    futrue_l = {}
    for i in range(20):         # 异步非阻塞的
        ret = tp.submit(func,i,b=i+1)
        ret.add_done_callback(print_func)  
# ret这个任务会在执行完毕的瞬间立即触发print_func函数,并且把任务的返回值对象传递到print_func做参数
# 异步阻塞 回调函数 给ret对象绑定一个回调函数,等待ret对应的任务有了结果之后立即调用print_func这个函数
# 就可以对结果立即进行处理,而不用按照顺序接收结果处理结果
View Code

回调函数的例子

from concurrent.futures import ThreadPoolExecutor
import requests
import os

def get_page(url):    # 访问网页,获取网页源代码   线程池中的线程来操作
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

def parse_page(res):   # 获取到字典结果之后,计算网页源码的长度,把https://www.baidu.com : 1929749729写到文件里   线程任务执行完毕之后绑定回调函数
    res=res.result()
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)

if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]
    # 获得一个线程池对象 = 开启线程池
    tp = ThreadPoolExecutor(4)
    # 循环urls列表
    for url in urls:
# 得到一个futrue对象 = 把每一个url提交一个get_page任务
        ret = tp.submit(get_page,url)
        # 给futrue对象绑定一个parse_page回调函数
        ret.add_done_callback(parse_page)   
        # 谁先回来谁就先写结果进文件
View Code

 

协程

线程协程的区别:

  • 协程没有锁,协程又称微线程
  • 线程和协程不同的是,线程是抢占式调度切换,而协程是需要自己调度
  • 线程和进程,调度是CPU决定的,而协程就是上帝,在一个线程中规定某个代码块的执行顺序
协程:本质是一个线程
     能够在一个线程内的多个任务之间来回切换
    节省io操作的时间也只能是和网络操作相关的
    特点:数据安全,用户级别,开销小,不能利用多核,能够识别的io操作少
gevent 第三方模块  完成并发的socket server
    协程对象.spawn(func,参数)
    能识别的io操作也是有限的
    并且要想让gevent能够识别一些导入的模块中的io操作
    from gevent import monkey;monkey.patch_all()
asyncio 内置模块
    await 写好的asyncio中的阻塞方法
    async 标识一个函数时协程函数,await语法必须用在async函数中

 

切换 并 规避io 的两个模块
gevent =  利用了  greenlet    底层模块完成的切换 + 自动规避io的功能
asyncio = 利用了  yield    底层语法完成的切换 + 自动规避io的功能
    tornado 异步的web框架
    yield from - 更好的实现协程
    send - 更好的实现协程
    asyncio模块 基于python原生的协程的概念正式的被成立
    特殊的在python中提供协程功能的关键字 : aysnc await
# 用户级别的协程还有什么好处:
    # 减轻了操作系统的负担
    # 一条线程如果开了多个协程,那么给操作系统的印象是线程很忙,这样能多争取一些时间片时间来被CPU执行,程序的效率就提高了

 

gevent

import gevent

def func():    # 带有io操作的内容写在函数里,然后提交func给gevent
    print('start func')
    gevent.sleep(1)  
# gevent.sleep是一个特殊的,time.sleep在这里不行
# 如果想用time就要在用下面的代码
    print('end func')

g1 = gevent.spawn(func)
g2 = gevent.spawn(func)
g3 = gevent.spawn(func)
gevent.joinall([g1,g2,g3])

 

time

import time
print(time.sleep)
# 这里的time和from gevent import mockey里的不同

from gevent import monkey
monkey.patch_all()        
import time
import gevent

def func():    # 带有io操作的内容写在函数里,然后提交func给gevent
    print('start func')
    time.sleep(1)
    print('end func')

g1 = gevent.spawn(func)
g2 = gevent.spawn(func)
g3 = gevent.spawn(func)
gevent.joinall([g1,g2,g3])
# 阻塞 直到协程g1任务执行结束
# 要有阻塞才能执行

gevent(server和client)

基于gevent协程实现socket并发

server

import socket
print(socket.socket)          # 在patch all之前打印一次
from gevent import monkey    # gevent 如何检测是否能规避某个模块的io操作呢?
monkey.patch_all()
import socket
import gevent
print(socket.socket)           # 在patch all之后打印一次,如果两次的结果不一样,那么就说明能够规避io操作
def func(conn):
    while True:
        msg = conn.recv(1024).decode('utf-8')
        MSG = msg.upper()
        conn.send(MSG.encode('utf-8'))

sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()

while True:
    conn,_ = sk.accept()
    gevent.spawn(func,conn)
View Code

client

import time
import socket
from threading import Thread
def client():
    sk = socket.socket()
    sk.connect(('127.0.0.1',9001))
    while True:
        sk.send(b'hello')
        msg = sk.recv(1024)
        print(msg)
        time.sleep(0.5)

for i in range(500):
    Thread(target=client).start()
View Code

 

asyncio定义协程函数

import asyncio

async def func(name):
    print('start',name)
    # await 可能会发生阻塞的方法
    # await 关键字必须写在一个async函数里
    await asyncio.sleep(1)
    print('end')

loop = asyncio.get_event_loop()
loop.run_until_complete(func("alex")) # 单个任务
# loop.run_until_complete(asyncio.wait([func('alex'),func('太白')]))接收多个,接受列表

 

 

 

 

 

 

 

posted @ 2019-08-13 22:00  驰念  阅读(341)  评论(0编辑  收藏  举报