并发编程 (进程,线程,协程,I/O模型)内含几种队列

目录

一. 多进程(process)

一. 开启多进程

2. 多个子进程

3.以类的方法开启子进程

4. 进程之间数据隔离

5.  守护进程

二. 锁

三. 信号量(同一时间指定几个可以执行)

四. 事件

五. 队列

六. 生产着消费者 队列

1. 基于queue

2. 基于JoinableQueue

七. 管道, Manager 

八. 进程池

1. map 第二个参数要是可迭代的

2. 进程池 异步的apply_async

九. 回调函数

二. 多线程 (threading)

一. 开启多线程

二. 线程之间数据共享

三. join  join感知子线程结束,等待子线程结束后继续执行(主等子)

四. 守护线程    子线程等待主线程运行完毕,才会结束被销毁,(子等主)

五. 线程锁

1. 互斥锁(感觉就是普通的锁)

2. 死锁

3. 递归锁 RLock  (解决死锁)

六. 条件

七. 信号量Semaphore和BoundedSemaphore

BoundedSemaphore

八. 条件 (和信号量像)

九. 队列和栈

1. 队列(先进先出)

2. 栈(先进后出)

3. import queue 和 from multiprocessing import Queue 

十. 进程池和线程池 (依赖于新的内置模块, 新模块的进程池和上面的进程池都可实现)

1. submit提交 可以接受返回值

2. map提交,参数是可迭代的,不能接受返回值

3. 回调函数

三. 协程(greenlet, Gevent)

1. greenlet

2. Gevent(更牛逼)

3. 同步异步

4. 事件

5. 生产者消费者模型

6. 协程的本地变量

四. I/O模型

1. 阻塞I/O模型

2. 同步非阻塞I/O模型

3. 多路复用I/O模型

4. 异步非阻塞I/O模型

五 .双向队列


一. 多进程(process)

一. 开启多进程

from multiprocessing import Process
import time


def func(arg, arg1):
    print(arg, arg1)
    time.sleep(1)
    print('子进程')


if __name__ == "__main__":
    p = Process(target=func, args=('参数', '参数1'))
    p.start()
    p.join()          # 感知一个子进程, 将后面的代码异步改为同步,
    print('父进程')    # 有了join会在子进程结束后执行后面的代码


# 参数 参数1
# 子进程
# 父进程
  1. p.start():启动进程,并调用该子进程中的p.run()
  2. p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 
  3. p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
  4. p.is_alive():如果p仍然运行,返回True
  5. p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程  
  6. p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
  7.  p.name:进程的名称
  8. p.pid:进程的pid
  9.  p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
  10. p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

2. 多个子进程

import time
from multiprocessing import Process


def f(name):
    print('hello', name)
    time.sleep(1)


if __name__ == '__main__':
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=('bob',))
        p.start()
        p_lst.append(p)
        p.join()                      # 等所有子进程执行完,再执行join后面的主进程代码
    [p.join() for p in p_lst]         # 不加这句也可以正常执行, 不过加了便于理解  
    print('父进程在执行')

3.以类的方法开启子进程

import os
from multiprocessing import Process


class MyProcess(Process):
    def __init__(self, arg1, arg2):
        super(MyProcess, self).__init__()       # 继承父类的init,要不报错, 在父类的init基础上增加属性
        self.arg1 = arg1
        self.arg2 = arg2

    def run(self):
        print(self.pid)
        print(self.name)
        print(self.arg1)
        print(self.arg2)


if __name__ == "__main__":
    print('主进程', os.getpid())
    p1 = MyProcess(1, 2)
    p1.start()
    p2 = MyProcess(3, 4)
    p2.start()

4. 进程之间数据隔离

from multiprocessing import Process


def work():
    global n
    n = 10
    print('子进程内: ', n)


if __name__ == '__main__':
    n = 100
    p = Process(target=work)
    p.start()
    print('主进程内: ', n)


# 主进程内:  100
# 子进程内:  10

5.  守护进程

守护进程会在主进程代码执行结束后就终止, 守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

#1.对主进程来说,运行完毕指的是主进程代码运行完毕
#2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
# 守护进程, 主进程结束, 子进程也跟着结束

def func():
    while True:
        time.sleep(0.2)
        print('我是没死的子进程')


if __name__ == "__main__":
    p = Process(target=func)
    p.daemon = True                 # 设置子进程为守护进程, 一定要在start前执行
    p.start()
    i = 0
    while i < 5:
        print("我是主进程")
        time.sleep(1)
        i += 1
# 守护进程 会 随着 主进程的代码执行 完毕 而 结束

二. 锁

import time
import json
from multiprocessing import Lock
from multiprocessing import Process




def show(i):
    with open('tick.txt') as f:
        dic = json.load(f)
    print('余票', '%s' % dic['ticket'])


def buy_ticket(i, lock):
    lock.acquire()                                       # 拿到锁
    with open('tick.txt') as f:
        dic = json.load(f)
        time.sleep(0.1)
    if dic['ticket'] > 0:
        dic['ticket'] -= 1
        print('\033[32m%s买到票了\033[0m' % i)
    else:
        print('\033[31m%s没买到票\033[0m' % i )
    with open('tick.txt', 'w') as f:
        json.dump(dic, f)
    lock.release()                                      # 还锁


if __name__ == "__main__":
    for i in range(10):
        p = Process(target=show, args=(i,))
        p.start()
    lock = Lock()                                       # 实例化个锁
    for i in range(10):
        p = Process(target=buy_ticket, args=(i, lock))
        p.start()
tick.txt


{"ticket": 3}

    锁保证了数据安全,但是却把多线程逼成了单线程,效率降低了

三. 信号量(同一时间指定几个可以执行)

import time
from multiprocessing import Process
import random


# 信号量  同一时间只有指定几个可以执行
from multiprocessing import Semaphore


def room(i, sem):
    sem.acquire()
    print('%s走进room' % i)
    time.sleep(random.randint(1, 5))
    print('%s走出room' % i)
    sem.release()


if __name__ == "__main__":
    sem = Semaphore(4)                        # 指定4个
    for i in range(20):
        p = Process(target=room, args=(i, sem))
        p.start()

四. 事件

# 事件
from multiprocessing import Event
e = Event()                        # 创建一个事件
print(e.is_set())                  # 查看一个事件的状态, 默认是阻塞
print(123456)
e.set()                            # 将false改为True
print(e.is_set())
e.wait()                           # 根据e.is_set()的值来判断是否阻塞
print(123456)                      # 上面是False, 所以这个不打印
e.clear()                          #  之前一直是True 将事件状态改为False
print(e.is_set())
e.wait()                           # 也可以加上参数wait(10),就是即使为false也就等10秒
print(123456)

事件实例

# 事件实例 红绿灯

import random
import time
from multiprocessing import Event, Process



def cars(e, i):
    if not e.is_set():               # 如果是False
        print('car%i在等待' % i)
        e.wait()                     # 阻塞直到得到一个事件变成True的信号
    print('\033[0;32;40m car %i通过\033[0m' % i)


def light(e):
    while True:
        if e.is_set():
            e.clear()
            print('\033[31m红灯亮了\033[0m')
        else:
            e.set()
            print('\033[32m绿灯亮了\033[0m')
        time.sleep(2)


if __name__ == "__main__":
    e = Event()
    traffic = Process(target=light, args=(e, ))
    traffic.start()
    for i in range(20):
        car = Process(target=cars, args=(e, i))
        car.start()
        time.sleep(random.random())

五. 队列

from multiprocessing import Queue

q = Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5)
# 因为设置了队列长度为5, 所以在将一个会阻塞
print(q.full())      # 队列是否已满
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())

# 再get 会阻塞
print(q.empty())     # 是否为空
  1. q.get( [ block [ ,timeout ] ] ) 
    返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。
  2. q.put(item [, block [,timeout ] ] ) 
    将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
  3. q.qsize() 
    返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。
  4. q.empty() 
    如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。
  5. q.full() 
    如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。。
  6. q.get_nowait( ) 
    同q.get(False)方法。

六. 生产着消费者 队列

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

1. 基于queue

import time
import random
from multiprocessing import Process, Queue


def producer(name, q):
    for i in range(5):
        time.sleep(random.uniform(0, 1))
        q.put(i)
        print("{}生产了{}".format(name, i))


def consumer(name, q):
    while True:
        some_ting = q.get()
        if some_ting is None:
            break
        print("{}消费了{}".format(name, some_ting))
        time.sleep(random.uniform(0, 1))


if __name__ == "__main__":
    q = Queue(20)
    p1 = Process(target=producer, args=("a", q))
    p2 = Process(target=producer, args=("b", q))
    c1 = Process(target=consumer, args=('c', q))
    c2 = Process(target=consumer, args=('b', q))
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    p1.join()
    p2.join()
    q.put(None)
    q.put(None)

2. 基于JoinableQueue

import random
import time
from multiprocessing import Process, JoinableQueue


def consumer(q, name):
    while True:
        food = q.get()
        print('\033[31m%s消费了 %s \033[0m' % (name, food))
        time.sleep(random.randint(1, 3))
        q.task_done()             # 计数器-1


def producer(name, food, q):
    for i in range(5):
        time.sleep(random.randint(1, 3))
        f = '%s生产了%s%s' % (name, food, i)
        print(f)
        q.put(f)     # 每加个数据 数 据+1
    q.join()         # 阻塞 直到 一个队列数据 全部执行完毕, 拉长生产者生命周期,直到消费者把队列中的资源消耗完才结束


if __name__ == "__main__":
    q = JoinableQueue(20)
    p1 = Process(target=producer, args=('aki', '包子', q))
    p2 = Process(target=producer, args=('desky', '馒头', 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()                    # 有了join会在子进程结束后执行后面的代码
    p2.join()

# consumer 中把所有任务消耗完
# producer 中的join感知道,停止堵塞
# 所有的producer进程结束
# 主进程中的p.join结束
# 主进程中代码结束
# 守护进程(消费者的进程)结束



七. 管道, Manager 

进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的

管道

from multiprocessing import Pipe, Process


def func(conn1, conn2):
    conn2.close()                # conn2 用来发送,先关闭
    while True:
        try:
            msg = conn1.recv()
            print(msg)
        except EOFError:
            conn1.close()
            break


if __name__ == "__main__":
    conn1, conn2 = Pipe()
    Process(target=func, args=(conn1, conn2)).start()
    conn1.close()                  # conn1 用来接收 先关闭
    for i in range(20):
        conn2.send("are you ok")
    conn2.close()

 

manager + 锁 实现子进程修改主进程变量, 不加锁数据不安全

from multiprocessing import Manager, Process, Lock


def main(dic, lock):
    lock.acquire()
    dic['count'] -= 1
    lock.release()


if __name__ == "__main__":
    m = Manager()
    l = Lock()
    dic = m.dict({'count': 100})
    p_list = []
    for i in range(50):
        p = Process(target=main, args=(dic, l))
        p.start()
        p_list.append(p)
    for i in p_list:
        i.join()
    print('主进程', dic)

 

八. 进程池

定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。

1. map 第二个参数要是可迭代的

from multiprocessing import Pool


def func(n):
    for i in range(10):
        print(n+1)


if __name__ == "__main__":
    pool = Pool(5)
    pool.map(func, range(100))            # 必须是可迭代的
    # map后面的range 是说让range执行几次
import time
from multiprocessing import Pool
import os


def func(n):
    print('start func%s' % n, os.getpid())
    time.sleep(1)
    print('end func%s' % n, os.getpid())


if __name__ == "__main__":
    p = Pool(5)
    for i in range(20):
        p.apply(func, args=(i,))

# 是同步的
# start func0 8320
# end func0 8320
# start func1 304
# end func1 304

2. 进程池 异步的apply_async

import time
from multiprocessing import Pool
import os


def func(n):
    print('start func%s' % n, os.getpid())
    time.sleep(1)
    print('end func%s' % n, os.getpid())


if __name__ == "__main__":
    p = Pool(5)
    for i in range(20):
        p.apply_async(func, args=(i,))
    p.close()              # 没有新任务再提交了, 结束进程池提交事务
    p.join()               # 感知进程池中任务结束

# 异步apply_async用法:如果使用异步提交的任务,主进程需要使用jion,等待进程池内任务都处理完,然后可以用get收集结果
# 否则,主进程结束,进程池可能还没来得及执行,也就跟着一起结束了
1 方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法
2 obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。
3 obj.ready():如果调用完成,返回True
4 obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常
5 obj.wait([timeout]):等待结果变为可用。
6 obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数

map: 异步 自带close和join, 返回结果[]

apply: 同步的,只有当func执行之后,才会继续向下执行其他代码, 返回值是func的return

apply_async: 异步的, 但func被注册进入一个进程之后,程序继续向下执行,返回值,为对象, obj.get()。 要先close()再join()来保持子进程和主进程代码的同步性

九. 回调函数

from multiprocessing import Pool


def func1(n):
    print('func1')
    return n*n

# 回调函数 在主进程里执行
# 在 主进程执行
def fun2(nn):
    print('func2')
    print('nn')


if __name__ == "__main__":
    p = Pool(5)
    p.apply_async(func1, args=(10,), callback=fun2)
    # 现执行func1 在执行func2 把func1的返回值 传给func2作为参数
    p.close()
    p.join()


二. 多线程 (threading)

一. 开启多线程

1 通过函数

from threading import Thread
import time


# 开启10个线程
def func(n):
    time.sleep(1)
    print(n)


for i in range(10):
    t = Thread(target=func, args=(i,))
    t.start()

2. 通过类

# init 和 run 是固定不变的
class MyTread(Thread):

    def __init__(self, arg):
        super().__init__()        # 继承父类的init
        self.arg = arg            # 写init 就可以给run 传参数

    def run(self):
        time.sleep(1)
        print(self.arg)


for i in range(10):
    t = MyTread(10)
    t.start()

二. 线程之间数据共享

from multiprocessing import Process
from threading import Thread


def func():
    global n
    n = 0
    print('子', n)


if __name__ == "__main__":
    n = 100
    t = Thread(target=func)
    t.start()
    t.join()
    print('主', n)
    # 主线程打印出来的n是0, 说明子线程修改了主线程的变量

    # 如下, 开启个子进程, 打印n依然是100, 因为开启的子进程没法改主进程的的变量
    # n = 100
    # p = Process(target=func)
    # p.start()
    # p.join()
    # print('主', n)

三. join  join感知子线程结束,等待子线程结束后继续执行(主等子)

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=('egon',))
    t.start()
    t.join()                 # 等子线程结束后继续执行
    print('主线程')
    print(t.is_alive())
    '''
    egon say hello
    主线程
    False
    '''

四. 守护线程    子线程等待主线程运行完毕,才会结束被销毁,(子等主)

    即 子线程会随着主线程结束而结束

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

3 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
4 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

import time
from threading import Thread


def func():
    while True:
        print('*'*10)
        time.sleep(1)


def func1():
    print('func1')
    time.sleep(5)


if __name__ == "__main__":
    t = Thread(target=func, )
    t.daemon = True           # 设置守护线程,子线程会随着主线程结束而结束
    t.start()
    t2 = Thread(target=func1)
    t2.start()
    print('主线程')
    # t2是主线程的一部分,睡了5秒, 这5秒守护线程,也一直执行, 但t2的funct1执行完,主线程结束,t线程结束
    # 即线程在其他非守护线程运行完毕后才算运行完毕

"""
**********
func1
主线程
**********
**********
**********
**********

"""

五. 线程锁

1. 互斥锁(感觉就是普通的锁)

 开启十个子线程, 每个子线程对全局变量-1

# 加锁 速度是真TM慢  Lock 互斥锁

import time
from threading import Lock, Thread


def func(lock):
    global n
    lock.acquire()
    temp = n
    time.sleep(0.2)
    n = temp - 1
    lock.release()
# tem 模拟寄存器操作


if __name__ == "__main__":
    n = 10
    t_lst = []
    lock = Lock()
    for i in range(10):
        t = Thread(target=func, args=(lock,))
        t.start()
        t_lst.append(t)
    for t in t_lst:
        t.join()         # 感知每个子线程结束,在打印n
    print(n)
    # 0

2. 死锁

from threading import Lock

mutexA = Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

一直干耗着,谁都不肯先解锁

3. 递归锁 RLock  (解决死锁)

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

from threading import RLock

mutexA = RLock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

# 123

递归锁的例子

from threading import RLock, Thread

fork_lock = noodle_lock = RLock()  


def eat1(name):
    noodle_lock.acquire()
    print('%s拿到面条' % name)
    fork_lock.acquire()
    print('%s拿到叉子' % name)
    print('%s吃面' % name)
    fork_lock.release()
    noodle_lock.release()


Thread(target=eat1, args=('aki', )).start()
Thread(target=eat1, args=('えなこ',)).start()


"""
aki拿到面条
aki拿到叉子
aki吃面
えなこ拿到面条
えなこ拿到叉子
えなこ吃面
""

六. 条件

  同进程的条件

七. 信号量Semaphore和BoundedSemaphore

              指定同一时间有几个线程执行代码

Semaphore

# 信号量
# 被 acquire和release 保护起来的代码, 同一时间只能由指定数量的线程执行
import time
from threading import Semaphore, Thread


def func(sem, a, b):
    sem.acquire()
    time.sleep(1)
    print(a+b)
    sem.release()


sem = Semaphore(4)
for i in range(10):
    t = Thread(target=func, args=(sem, i, i+5))
    t.start()

BoundedSemaphore

它有计数功能, 这个计数器的值永远不会超过它的初始值

import random
import time
from atexit import register
from threading import BoundedSemaphore, Lock, Thread


lock = Lock()
MAX = 5
candytray = BoundedSemaphore(MAX)       


# 向机器里加糖果
def refill():
    lock.acquire()                  # 加锁
    print('Refilling candy ...')
    try:
        candytray.release()        # 让信号量计数器+1,加糖 如果超过5个, 则会触发异常
    except ValueError:
        print('full, skipping')
    else:
        print('ojbk')
    lock.release()              # 释放锁


# 买糖
def buy():
    lock.acquire()
    print('Buying candy')
    if candytray.acquire(False):         # 有False 说明非阻塞,让信号量—1,if 语句看看能不能拿到糖果
        print('ok')
    else:
        print('empty skipping')
    lock.release()


def producer(loops):
    for i in range(loops):
        refill()
        time.sleep(random.randrange(3))


def consumer(loops):
    for i in range(loops):
        buy()
        time.sleep(random.randrange(3))


def _main():
    print('starting at:', time.ctime())
    nloops = random.randrange(2, 6)
    print('THE CANDY MACHINE (full with %d bars)!' % MAX)
    Thread(target=consumer, args=(random.randrange(nloops, nloops + MAX + 2),)).start()
    # 里面的数学操作,是的真正消费的糖果数可能会比生产者放入得多,否则不会出现向空机器要糖的结果
    Thread(target=producer, args=(nloops,)).start()


@register
def _atexit():
    print('all DONE at:', time.ctime())
# 让解释器在退出脚本前能执行它


if __name__ == "__main__":
    _main()

 

八. 条件 (和信号量像)

from threading import Condition
# acquire, release,
# 一个条件被创建之初 默认有个False状态
# False 状态 会影响wait一直处于等待状态
# notify(int) 制造int把钥匙
# 钥匙是一次性的, 造完还回来就不能用了, 还得再造


def func(con, i):
    con.acquire()
    con.wait()                 # 等钥匙
    print('在第%s个循环里' % i)
    con.release()


con = Condition()
for i in range(10):
    Thread(target=func, args=(con, i)).start()
while True:
    num = int(input('>>>'))
    con.acquire()
    con.notify(num)          # 造钥匙
    con.release()

上面的代码一直input,要求输入个int。相当于让notify造了指定的几把钥匙,也就是指定了先运行几个线程,但线程总数超过10会报错。

九. 队列和栈

1. 队列(先进先出)

普通队列(Queue)

# 普通队列

import queue

q = queue.Queue()
q.put(1)
q.get()
q.get()            # 只put1个,再get会阻塞
# q.get_nowait()   # get不到就报错, 如果不写它,get不到就堵塞
# q.put_nowait()   # put 类似上面

优先队列(PriorityQueue)

# 优先队列


# 优先队列 第一个参数是优先级, 越小优先级越高,越先get到
import queue
q = queue.PriorityQueue()
q.put((20, 'a'))
q.put((1, 'e'))
q.put((1, 'c'))       # 优先级 相同 按asci码,打印在前面的c
q.put((45, 'r'))
print(q.get())

2. 栈(先进后出)

import queue

# 栈
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())

3. import queue 和 from multiprocessing import Queue 

用法相同但是不是一个玩意儿

Queue.Queue是进程内非阻塞队列,multiprocess.Queue是跨进程通信队列。

1.from queue import Queue 
这个是普通的队列模式,类似于普通列表,先进先出模式,get方法会阻塞请求,直到有数据get出来为止

2.from multiprocessing.Queue import Queue(各子进程共有)
这个是多进程并发的Queue队列,用于解决多进程间的通信问题。普通Queue实现不了。例如来跑多进程对一批IP列表进行运算,运算后的结果都存到Queue队列里面,这个就必须使用multiprocessing提供的Queue来实现

 

十. 进程池和线程池 (依赖于新的内置模块, 新模块的进程池和上面的进程池都可实现)

 

注意: 这个模块当需要进程池时只要把ThreadPoolExecutor改为ProcessPoolExecutor就好了

1 介绍
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用

2 基本方法
#submit(fn, *args, **kwargs)
异步提交任务

#map(func, *iterables, timeout=None, chunksize=1)
取代for循环submit的操作

#shutdown(wait=True)
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前

#result(timeout=None)
取得结果

#add_done_callback(fn)
回调函数

# done()
判断某一个线程是否完成

# cancle()
取消某个任务

1. submit提交 可以接受返回值

# 5个5个执行
import time
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor


def func(n):
    time.sleep(2)
    print(n)
    return n*n


tpool = ThreadPoolExecutor(max_workers=5)    # 不要超过cpu个数*5, 定义池子大小

# 用submit提交, 可接受返回值
t_lst = []      # 接收上面的返回值
for i in range(20):
    t = tpool.submit(func, i)               # 用submit提交, 可以有返回值, t为返回值
    t_lst.append(t)
tpool.shutdown()                           # 完成close 和 join
print('主线程')
for t in t_lst:                            # 打印返回值
    print("结果", t.result())              # result拿到值

2. map提交,参数是可迭代的,不能接受返回值

import time
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor


def func(n):
    time.sleep(2)
    print(n)
    return n*n


tpool = ThreadPoolExecutor(max_workers=5)    # 不要超过cpu个数*5, 定义池子大小
tpool.map(func, range(20))

3. 回调函数

import time
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor


def func(n):
    time.sleep(2)
    print(n)
    return n*n


def call_back(m):
    print('回调结果是 %s' % m.result())    # result拿到结果


tpool = ThreadPoolExecutor(max_workers=5)    # 不要超过cpu个数*5, 定义池子大小
for i in range(20):
    t = tpool.submit(func, i).add_done_callback(call_back)
tpool.shutdown()                            # 完成close 和 join
print('主线程')

三. 协程(greenlet, Gevent)

协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。 协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。

优点如下:

1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级

2. 单线程内就可以实现并发的效果,最大限度地利用cpu

缺点如下:

1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程

2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

总结协程特点:

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈

自己的理解:

                   协程就是线程里的一个或多个函数,只能一个接着一个运行,不能同时执行,相对于进程和线程是操作系统控制的而言,它是用户自己控制的。而且能保存状态,切换回来接着上次切换走的状态接着来。

 

1. greenlet

单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度,

当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。

我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。

from greenlet import greenlet


def eat():
    print('eat start')
    g2.switch()             # 切换到g2的paly()
    print('eat end')
    g2.switch()


def play():
    print('play start')
    g1.switch()           # 切换到g1的eat()
    print('play end')


g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch()               # 切换到eat, 执行eat


# eat start
# play start
# eat end
# play end

2. Gevent(更牛逼)

g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的

g2=gevent.spawn(func2)

g1.join() #等待g1结束

g2.join() #等待g2结束

#或者上述两步合作一步:gevent.joinall([g1,g2])

g1.value#拿到func1的返回值

started属性/ready()方法:判断协程是否已启动。

successful()方法:判断协程是否成功运行且没有抛出异常。
import gevent
from gevent import monkey


def eat(name):
    print('%s eat 1' % name)
    gevent.sleep(2)
    # time.sleep(2) 
    print('%s eat 2' % name)


def play(name):
    print('%s play 1' % name)
    gevent.sleep(1)
    # time.sleep(1) 
    print('%s play 2' % name)


g1 = gevent.spawn(eat, 'aki')
g2 = gevent.spawn(play, name='aki')
g1.join()                          # 等待协程g1的结束
g2.join()                          # 等待协程g2的结束
# 或者gevent.joinall([g1,g2])
print('主')


# aki eat 1
# aki play 1
# aki play 2
# aki eat 2
# 主

# 注意这个执行顺序,gevent,遇到阻塞自动切换,先执行eat,遇到睡两秒,切到paly,遇到睡一秒,切换到eat,但是eat睡了两秒,还在睡,阻塞,再切换到play,


注意:

这里的gevent.sleep()相当于time.sleep()。 因为直接用time.sleep()不识别。

所以要from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前

必须要在前面。那个分号就是分行的意思,也可以协程两行。

3. 同步异步

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


def task():
    time.sleep(1)
    print(123)


# 同步的叫什么无所谓, 跟协程也没啥关系
def synchronous():
    for i in range(10):
        task()


# 异步的叫什么无所谓
def asynchronous():
    g_lst = []
    for i in range(10):
        g = gevent.spawn(task)
        g_lst.append(g)
    gevent.joinall(g_lst)   # 相当于for g in g_lst.join(), 等待10个协程全部结束


synchronous()          # 同步的, 一个一个打印123, 打印10个
asynchronous()         # 异步的 同时打印10个123

4. 事件

greenlet协程间的异步通讯可以使用事件(Event)对象。
该对象的”wait()”方法可以阻塞当前协程,而”set()”方法可以唤醒之前阻塞的协程。
在下面的例子中,几个waiter协程都会等待事件evt,当setter协程在3秒后设置evt事件,所有的waiter协程即被唤醒。

event

import gevent
from gevent.event import Event

evt = Event()


def setter():
    print('Wait for me')

    gevent.sleep(3)             # 3秒后唤醒所有在evt上等待的协程
    print("Ok, I'm done")

    evt.set()                   # 唤醒


def waiter():
    print("I'll wait for you")

    evt.wait()  # 等待
    print('Finish waiting')


gevent.joinall([
    gevent.spawn(setter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
])



"""
Wait for me
I'll wait for you
I'll wait for you
I'll wait for you
Ok, I'm done
Finish waiting
Finish waiting
Finish waiting
"""
AsyncResult 它可以在唤醒时传递消息
from gevent.event import AsyncResult
import gevent

aevt = AsyncResult()


def setter():
    print('Wait for me')
    gevent.sleep(3)                 # 3秒后唤醒所有在evt上等待的协程
    print("Ok, I'm done")
    aevt.set('Hello!')             # 唤醒,并传递消息


def waiter():
    print("I'll wait for you")
    message = aevt.get()                          # 等待,并在唤醒时获取消息
    print('Got wake up message: %s' % message)    # 必须叫message


gevent.joinall([
    gevent.spawn(setter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
])


"""
Wait for me
I'll wait for you
I'll wait for you
I'll wait for you
Ok, I'm done
Got wake up message: Hello!
Got wake up message: Hello!
Got wake up message: Hello!
"""

5. 生产者消费者模型

import gevent
from gevent.queue import Queue

products = Queue()


def consumer(name):
    while True:
        try:
            print('%s got product %s' % (name, products.get_nowait()))   # 不阻塞,get不到直接抛异常
            gevent.sleep(0)
        except gevent.queue.Empty:
            break

    print('Quit')


def producer():
    for i in range(1, 10):
        products.put(i)


gevent.joinall([
    gevent.spawn(producer),
    gevent.spawn(consumer, 'steve'),
    gevent.spawn(consumer, 'john'),
    gevent.spawn(consumer, 'nancy'),
])

6. 协程的本地变量

同线程类似,协程也有本地变量,也就是只在当前协程内可被访问的变量:

通过将变量存放在local对象中,即可将其的作用域限制在当前协程内,当其他协程要访问该变量时,就会抛出异常。不同协程间可以有重名的本地变量,而且互相不影响。因为协程本地变量的实现,就是将其存放在以的”greenlet.getcurrent()”的返回为键值的私有的命名空间内。

import gevent
from gevent.local import local

data = local()


def f1():
    data.x = 1
    print(data.x)


def f2():
    try:
        print(data.x)

    except AttributeError:
        print('x is not visible')


gevent.joinall([
    gevent.spawn(f1),
    gevent.spawn(f2)
])

# 1
# x is not visible

 


 

四. I/O模型

当一个read操作发生时,该操作会经历两个阶段:

1)等待数据准备 (Waiting for the data to be ready)
2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)

  1. 同步阻塞I/O模型
  2. 同步非阻塞I/O模型
  3. I/O多路模型
  4. 异步I/O模型
  5. 信号驱动I/O模型   (没啥用,不说)

同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。

1. 阻塞I/O模型

例子:排队买票,排三天买到一张票,在火车站一直等三天,什么也没干。

如图所示,用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。在等待数据和数据拷贝用户线程都被阻塞,一直等。

即用户需要等待read将socket中的数据读取到buffer后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。

2. 同步非阻塞I/O模型

例子:每隔12小时去火车站问有没有票,三天后买到一张票,期间可以干别的。

同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NONBLOCK。这样做用户线程可以在发起IO请求后可以立即返回。

如图所示,由于socket是非阻塞的方式,因此用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。

也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

    所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。

 缺点:1. 循环调用recv()将大幅度推高CPU占用率;         

           2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。

3. 多路复用I/O模型

例子:1 select/poll  去火车站买票,委托黄牛,然后每隔6小时打电话问黄牛,黄牛三天内买到票,然后去找黄牛要票

           2 epoll        委托黄牛, 黄牛拿到票,主动通知你去取票

IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。

如图所示,用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。

从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

# io多路复用   依赖windows, python 提供的select模块只是使用操作系统的功能
import select
import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 9999))
sk.setblocking(False)              # 设置成非阻塞, 默认阻塞
sk.listen()

read_list = [sk]       # 添加conn , 监听两个
while True:
    r_lst, w_lst, x_lst = select.select(read_list, [], [])   # 必须要三个列表参数,
    for i in r_lst:
        if i is sk:
            conn, addr = i.accept()
            read_list.append(conn)
        else:
            ret = i.recv(1024)
            if ret == b'':
                i.close()
                read_list.remove(i)
                continue
            print(ret)
            i.send(b'bai')

IO多路复用是最常使用的IO模型,但是其异步程度还不够“彻底”,因为它使用了会阻塞线程的select系统调用。因此IO多路复用只能称为异步阻塞IO,而非真正的异步IO。

4. 异步非阻塞I/O模型

例子:去火车站给售票员留下电话,有票的话会通知你,并送货上门。

如图所示,异步IO模型中,用户线程直接使用内核提供的异步IO API发起read请求,且发起后立即返回,继续执行用户线程代码。不过此时用户线程已经将调用的AsynchronousOperation和CompletionHandler注册到内核,然后操作系统开启独立的内核线程去处理IO操作。当read请求的数据到达时,由内核负责读取socket中的数据,并写入用户指定的缓冲区中。最后内核将read的数据和用户线程注册的CompletionHandler分发给内部Proactor,Proactor将IO完成的信息通知给用户线程(一般通过调用用户线程注册的完成事件处理函数),完成异步IO。

# # 异步非阻塞
import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8765))
sk.setblocking(False)              # 把socket当中所有阻塞方法都改变成非阻塞
sk.listen()
conn_l = []
del_conn = []
while True:
    try:
        conn, addr = sk.accept()   # 不会阻塞, 但是没人连会报 BlockingIOError的错
        print('建立连接', addr)
        conn_l.append(conn)
    except BlockingIOError:
        for con in conn_l:
            try:
                msg = con.recv(1024)
                if msg == b'':
                    del_conn.append(con)
                    continue
                print(msg)
                con.send(b'see you')
            except BlockingIOError:
                pass
        for con in del_conn:
            con.close()
            conn_l.remove(con)
        del_conn.clear()

 把socket当中所有阻塞方法都改变成非阻塞。当没连接时,整段代码实际都不运行(conn, addr = sk.accept(), 没人连接,直接跳到except BlockingIOError, 又因为conn_l = [],del_conn = []为空,for 里面都不执行)。当有了一个连接,conn_l.append(conn),conn_l里面有值。当建立连接后,会运行外层的except BlockingIOError(因为只能有一次连接),执行for con in conn_l。后面不解释了。 


五 .双向队列

>>> from collections import deque
>>> d = deque('ghi')                 # make a new deque with three items
>>> for elem in d:                   # iterate over the deque's elements
...     print elem.upper()
G
H
I

>>> d.append('j')                    # add a new entry to the right side
>>> d.appendleft('f')                # add a new entry to the left side
>>> d                                # show the representation of the deque
deque(['f', 'g', 'h', 'i', 'j'])

>>> d.pop()                          # return and remove the rightmost item
'j'
>>> d.popleft()                      # return and remove the leftmost item
'f'
>>> list(d)                          # list the contents of the deque
['g', 'h', 'i']
>>> d[0]                             # peek at leftmost item
'g'
>>> d[-1]                            # peek at rightmost item
'i'

>>> list(reversed(d))                # list the contents of a deque in reverse
['i', 'h', 'g']
>>> 'h' in d                         # search the deque
True
>>> d.extend('jkl')                  # add multiple elements at once
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
>>> d.rotate(1)                      # right rotation
>>> d
deque(['l', 'g', 'h', 'i', 'j', 'k'])
>>> d.rotate(-1)                     # left rotation
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])

>>> deque(reversed(d))               # make a new deque in reverse order
deque(['l', 'k', 'j', 'i', 'h', 'g'])
>>> d.clear()                        # empty the deque
>>> d.pop()                          # cannot pop from an empty deque
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in -toplevel-
    d.pop()
IndexError: pop from an empty deque

>>> d.extendleft('abc')              # extendleft() reverses the input order
>>> d
deque(['c', 'b', 'a'])

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值