协程,事件,队列,同步,异步,回调函数

Posted corei5

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了协程,事件,队列,同步,异步,回调函数相关的知识,希望对你有一定的参考价值。

协程

什么是协成?单个线程并发的处理多个任务,程序控制协成的切换+保持状态,协成的切换速度非常快,蒙蔽了操作系统的眼睛,让操作系统认为CPU一直在运行
进程或线程都是由操作系统控制CPU来回切换,遇到阻塞就切换执行其他任务,协成是程序控制的,霸占CPU执行任务,会在操作系统控制CPU之前来回切换,操作系统就认为CPU一直在运作

协程的优点:
    1.开销小
    2.运行速度快
    3.协程会长期霸占CPU只执行我程序里的所有任务
协程的缺点:
    1.协程属于微并发,处理任务不易过多
    2.协程的本质是单线程,无法利用多核,可以一个程序开启多个进程,每个进程开启多个线程.每个线程开启协程
协程处理io密集型比较好

协程的特点:
    1.必须在只有一个单线程里实现并发
    2.修改共享数据不需要加锁
    3.保持状态
    4.一个协程遇到io操作就自动切换到其他协程 
    
工作中:
​	一般在工作中我们都是进程+线程+协程的方式来实现并发,以达到最好的并发效果,如果是4核的cpu,一般起5个进程,每个进程中20个线程(5倍cpu数量),每个线程可以起500个协程,大规模爬取页面的时候,等待网络延迟的时间的时候,我们就可以用协程去实现并发。 并发数量 = 5 * 20 * 500 = 50000个并发,这是一般一个4cpu的机器最大的并发数。nginx在负载均衡的时候最大承载量就是5w个
#当一个任务没有阻塞的时候
import time
def task():
    res = 1
    for i in range(1,1000000):
        res += 1
def task1():
    res = 1
    for i in range(1,1000000):
        res -= 1
strt = time.time()
task()
task1()
end = time.time()
print(f"串行执行效率:{end - strt}") #串行执行效率:0.07783079147338867


#第一次接触协程是yield
#没有io阻塞
import time
def task():
    res = 1
    for i in range(1,1000000):
        res += 1
        yield res
def task1():
    g = task()
    res = 1
    for i in range(1,1000000):
        res -= 1
        next(g)
strt = time.time()
task()
task1()
end = time.time()
print(f"协成执行效率:{end - strt}")#协成执行效率:0.21143341064453125
纯计算密集型,没有io情况下,串行的执行效率高于协程


# 并不是真正的阻塞
import gevent
def eat(name):
    print(f"{name} eat 1")   #1
    gevent.sleep(2)    #模拟的是gevent可以识别的阻塞
    print(f"{name} eat 2")   #4
def play(name):
    print(f"{name} play 1")   #2
    gevent.sleep(1)
    print(f"{name} play 2")   #3
g1 = gevent.spawn(eat,\'八戒\')
g2 = gevent.spawn(play,name = \'悟空\')
g1.join()
g2.join()
print("主")                    #5



import threading
from gevent import monkey
import gevent
import time
monkey.patch_all()        # 打补丁:将下面的所有的任务的阻塞打上标记,遇到这个标记就切换

def eat():
    print(f"线程1:{threading.current_thread().getName()}")    #1
    print(\'eat food 1\')                                       #2
    time.sleep(2)                                             #3阻塞,去执行别的任务
    print(\'eat food 2\')                                       #8

def play():
    print(f"线程2:{threading.current_thread().getName()}")    #4
    print(\'play 1\')                                           #5
    time.sleep(1)                                             #6阻塞,再去执行别的任务,上一个任务还在阻塞,继续向下执行
    print(\'play 2\')                                           #7
g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
gevent.joinall([g1,g2])
print(f"{threading.current_thread().getName()}")              #9
注意在各个任务中的阻塞时间

Event事件

添加全局变量,修改全局变量,实现一个线程在某一个节点让下一个线程继续工作
import time
from threading import Thread
from threading import current_thread
flag = False
def task():
    print(f"{current_thread().name}检测服务器是否正常开启.....")
    time.sleep(2)
    global flag
    flag = True
def task1():
    while 1:
        time.sleep(1)
        print(f"{current_thread().name}正在连接服务器....")
        if flag:
            print(f"{current_thread().name}连接成功")
            return

if __name__ == \'__main__\':
    t1 = Thread(target=task1)
    t2 = Thread(target=task1)
    t3 = Thread(target=task1)
    t = Thread(target=task)

    t.start()
    t1.start()
    t2.start()
    t3.start()
    
    
import time
from threading import Thread
from threading import current_thread
from threading import Event
import random
event = Event()   #默认为False
def task1():
    print(f"{current_thread().getName()} 检测服务器知否开启...") #获取线程名字
    time.sleep(random.randint(1,3))
def task2():
    print(f"{current_thread().getName()}正在尝试连接服务器...")
    count = 1
    while count < 4:
        time.sleep(1)
        print(f"{current_thread().getName()}连接第{count}次")
        if count < 4:
            time.sleep(0.5)
            event.set() #将event状态修改为True
            print(\'连接成功\')
        count += 1
t1 = Thread(target=task1)
t2 = Thread(target=task2)
t1.start()
t2.start()



import time
from threading import Thread
from threading import current_thread
from threading import Event
import random
event = Event()
def check():
    print(f"{current_thread().name}检测服务器是否开启...")
    time.sleep(random.randint(1,3))
    event.set()
    print("连接成功")
def connect():
    count = 1
    while not event.is_set(): #event.is_set()判断状态是True或False
        if count == 4:
            print(\'连接次数过多,已断开\')
            break
        event.wait(1) #轮询检测event状态,如果为真,就向下执行,是个阻塞, # 只阻塞1秒,1秒之后如果还没有进行set 直接进行下一步操作.
        print(f"{current_thread().name}尝试连接{count}次")
        count += 1
    else:
        print(f"{current_thread().name}连接成功")
t1 = Thread(target=check)
t2 = Thread(target=connect)
t1.start()
t2.start()

线程队列

import queue
# 先进先出原则
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
# print(q.get(block=False)) #设置多取会报错
q.get(timeout=2)#阻塞两秒,后报错
# print(q.get())多去一个会阻塞



# 后进先出
q = queue.LifoQueue(4)
q.put(1)
q.put(2)
q.put(\'alex\')
q.put(\'太白\')

print(q.get())
print(q.get())
print(q.get())
print(q.get())
# 太白
# alex
# 2
# 1

优先级队列
最小的先取出
q = queue.PriorityQueue(4)
q.put((1,\'qq\'))
q.put((-2,\'qq1\'))
q.put((0,\'qq2\'))

print(q.get())
print(q.get())
print(q.get())

#(-2, \'qq1\')
#(0, \'qq2\')
#(1, \'qq\')

同步

同步调用(提交完任务后,就在原地等待任务执行完后,拿到结果,再执行下一行代码)
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
import random
import os
def task(i):
    print(f"{os.getpid()}开始任务")
    time.sleep(random.randint(1,3))
    print(f"{os.getpid()}结束任务")
    return i
if __name__ == \'__main__\':
    pool = ProcessPoolExecutor()
    for i in range(10):                #同时开启10个进程
        obj = pool.submit(task,i)      #类似于发布任务
        print(f"任务结果:{obj.result()}")  # 对象加result()就变成同步,获取的是运行状态.obj就为动态对象, (running,pending,finish)  动态对象.result()获取结果
                                       #obj.result() 必须等到这个任务完成后,返回了结果,在执行下一个任务
    pool.shutdown(wait=True)           #shutdown 让我的主进程等待进程池中所有子进程结束后,在执行,类似于join
                                       #在上一个进程池没有完成任务之前,不允许添加新的任务,一个任务通过一个函数实现,任务完成了他的返回值就是函数的返回值
    print("主")
  

异步

异步调用(提交完任务后,不再原地等待任务执行完)
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
import random
import os
def task(i):
    print(f"{os.getpid()}开始任务")
    time.sleep(random.randint(1,3))
    print(f"{os.getpid()}结束任务")
    return i

if __name__ == \'__main__\':
    pool = ProcessPoolExecutor()
    for i in range(10):
        pool.submit(task,i)
    pool.shutdown(wait=True) #等待进程池中所有子进程结束后在向下执行,类似于join
    print("主")
    
存在一个列表中,统一回收结果 ,把动态对象放在列表中,(容器)
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
import random
import os
def task(i):
    print(f"{os.getpid()}开始任务")
    time.sleep(random.randint(1,3))
    print(f"{os.getpid()}结束任务")
    return i

if __name__ == \'__main__\':
    pool = ProcessPoolExecutor()
    l = []
    for i in range(10):
        obj = pool.submit(task,i)
        l.append(obj)
    pool.shutdown(wait=True)
    for i in l:
        print(i.result())
    print("主")
虽然是同时发布了任务,但是在回收结果的时候,不能马上收到一个结束任务的返回值,只能等所有任务结束后统一收到结果 


异步调用的如何取值?
1.
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
import random
import os
import requests
# 模拟的就是爬取多个源代码 一定有IO操作
def task(url):
    ret = requests.get(url)
    if ret.status_code == 200:
        return ret.text
def parse(content):
    return len(content)

if __name__ == \'__main__\':
    ret = task(\'http://www.baidu.com\')
    print(parse(ret))

    ret = task(\'http://www.JD.com\')
    print(parse(ret))

    ret = task(\'http://www.taobao.com\')
    print(parse(ret))

    ret = task(\'https://www.cnblogs.com/jin-xin/articles/7459977.html\')
    print(parse(ret))
#这样写为执行一个task函数,在执行一个parse函数,执行效率低,一个任务执行2秒,20个任务就是40秒,耗时
#这两个函数为一个任务,当一个任务执行完成后再执行下一个任务,串行

2.

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
import random
import os
import requests
# 模拟的就是爬取多个源代码 一定有IO操作
def task(url):
    ret = requests.get(url)
    if ret.status_code == 200:
        return ret.text
def parse(content):
    return len(content)

if __name__ == \'__main__\':
#     开启一个线程池,并发执行
    url_list = [\'http://www.baidu.com\',
                \'http://www.JD.com\',
                \'http://www.taobao.com\',
                \'https://www.cnblogs.com/jin-xin/articles/7459977.html\'
    ]
    pool = ThreadPoolExecutor(4)
    obj_list = []

    for url in url_list:
        obj = pool.submit(task,url)
        obj_list.append(obj)
    pool.shutdown(wait=True)
#这个for循环把爬取的源码添加到一个类表中,对象,执行task函数
    for i in obj_list:
        print(parse(i.result()))
循环取出类表中的元素,当做参数传入parse函数中,进行数据分析
以上版本缺点:异步的发出10个任务,并发的执行任务,但是分析结果的流程是串行,没有做到结束一个任务就返回一个返回值,效率低

3.

def task(url):
    ret = requests.get(url)
    if ret.status_code == 200:
        return parse(ret.text)    #更改函数,直接返回并调用parse函数
def parse(content):
    return len(content)

if __name__ == \'__main__\':
#     开启一个线程池,并发执行
    url_list = [
        \'http://www.baidu.com\',
        \'http://www.JD.com\',
        \'http://www.JD.com\',
        \'http://www.JD.com\',
        \'http://www.taobao.com\',
        \'https://www.cnblogs.com/jin-xin/articles/7459977.html\',
        \'https://www.luffycity.com/\',
        \'https://www.cnblogs.com/jin-xin/articles/9811379.html\',
        \'https://www.cnblogs.com/jin-xin/articles/11245654.html\',
        \'https://www.sina.com.cn/\'
    ]
    pool = ThreadPoolExecutor(4)
    obj_list = []

    for url in url_list:
        obj = pool.submit(task,url)
        obj_list.append(obj)
    pool.shutdown(wait=True)

    for i in obj_list:
        print(i.result())#直接返回结果,不用再调用函数,直接全部打印出来结果

4.异步调用+回调函数**************************
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
import random
import os
import requests
def task(url):
    \'\'\'模拟的就是爬取多个源代码 一定有IO操作\'\'\'
    ret = requests.get(url)
    if ret.status_code == 200:
        return ret.text
def parse(obj):
    \'\'\'模拟对数据进行分析 一般没有IO\'\'\'
    print(len(obj.result()))
if __name__ == \'__main__\':
    # 开启线程池,并发并行的执行
    url_list = [
        \'http://www.baidu.com\',
        \'http://www.JD.com\',
        \'http://www.JD.com\',
        \'http://www.JD.com\',
        \'http://www.taobao.com\',
        \'https://www.cnblogs.com/jin-xin/articles/7459977.html\',
        \'https://www.luffycity.com/\',
        \'https://www.cnblogs.com/jin-xin/articles/9811379.html\',
        \'https://www.cnblogs.com/jin-xin/articles/11245654.html\',
        \'https://www.sina.com.cn/\'
    ]
    pool = ThreadPoolExecutor(4)
    for url in url_list:
        obj = pool.submit(task, url)
        obj.add_done_callback(parse)
        
        #add_done_callback回调函数,执行一个任务就返回一个函数的返回值,增加回调函数,发布完任务后直接执行本行,当同一进程执行完第一次任务后,主进程立即对其结果进行分析,不必等所有进程全部执行完再分析,提高了效率
线程池设置4个线程,异步发起10个任务,每次执行4个任务,执行完任务的时间肯定有先后顺序,先执行完的,将parse分析代码的任务
交给空闲线程去执行,然后这个线程再去执行其他任务,比爬取源码的任务就和分析数据的任务共同并发执行


异步回收收取结果的两种方式?
1.将所有任务的结果同意回收
2.完成一个任务,返回一个结果

异步+回调函数
回调函数:按顺序接收每个任务的结果,进行下一步处理
前提:
    异步处理的多个任务(io任务),回调函数处理的非io
区别:
    多进程,由主进程执行回调函数的代码
    多线程,由空闲线程执行回调函数的代码

以上是关于协程,事件,队列,同步,异步,回调函数的主要内容,如果未能解决你的问题,请参考以下文章

Swoole 中使用 TCP 异步服务器TCP 协程服务器TCP 同步客户端TCP 协程客户端

python38 1.线程一堆队列 2.事件Event 3.协程 4.断点续传

# 进程/线程/协程 # IO:同步/异步/阻塞/非阻塞 # greenlet gevent # 事件驱动与异步IO # SelectPollEpoll异步IO 以及selectors模块 # (示

异步回调,事件,线程池与协程

同步回调函数与异步回调函数

爬虫第四章 单线程+多任务异步协程