同步异步补充,事件,协程及其相关

阻塞  非阻塞
    阻塞: 程序遇到了IO操作   导致代码无法继续执行 交出了CPU执行权
    非阻塞: 没有IO操作 或者 即使遇到IO操作 也不阻塞代码执行
    阻塞  就绪  运行
指的是应用程序所处的状态
写程序时 要尽量减少IO操作

同步  异步
    同步: 发起一个任务后,必须原地等待任务执行结束 拿到一个明确的结果

    异步: 发起一个任务后,不需要等待,代码继续往下执行
指的是任务的发起方式

异步任务的效率高于同步
场景:当你的任务是不需要立即获取结果的 并且还有其他的任务需要处理 那就发起异步任务

如何发起异步任务: 多进程 多线程

同步和阻塞的区别:
    阻塞一定意味着 CPU切走了
    而同步有可能是因为计算任务比较耗时

from concurrent.futures import ProcessPoolExecutor

import time,random

def task(num):
    time.sleep(random.randint(1,3))
    # print(num ** 2)
    return num ** 2

if __name__ == '__main__':
    pool = ProcessPoolExecutor()

    fs = []
    for i in range(6):
        f = pool.submit(task,i) # 以异步方式发起任务
        #print(f.result()) # 获取任务的执行结果 即 task函数的返回值
        #  会把异步变为同步  并行变成串行
        fs.append(f)

    # 方式1
    # 按照顺序来获取结果 将导致 一些任务可能已经完成了但是 没有及时获取到结果
    # 例如 第二个任务先执行完毕 但是必须要等到第一个执行结束才能拿到结果
    # for f in fs:
    #     print(f.result())

    # shutdown   关闭进程池  会阻塞直到所有进程任务全部执行完毕
    pool.shutdown(wait=True)
    for f in fs:
        print(f.result())

    # 关闭之后不能提交新任务
    # pool.submit(task,10)

    print("Over")

 

直接使用线程或进程发起异步:

from multiprocessing import Process
from threading import  Thread

import time

def task():
    time.sleep(2)
    print("run....")

if __name__ == '__main__':
    p = Thread(target=task)
    p.start()
    print("over")

 

异步回调:

获取异步任务结果的方式

爬虫:
    1.获取到HTML文档
    2.从文档中取出需要的数据

回调:其实说的是回调函数
    给异步任务绑定一个函数,当任务完成时会自动调用该函数

具体使用:
    当你往poll中添加了一个异步任务,会返回一个表示结果的对象
    有一个对象绑定方法 add_done_callback  需要一个函数做为参数
    注意:回调函数 必须有且只有一个参数 就是对象本身
        通过对象.result来获取结果
    回调函数交给子线程来执行  谁有空谁处理

优点: 不用原地等待,任务结果可以立即获取到

 

from concurrent.futures import ThreadPoolExecutor
import requests
import threading

#生产
def get_data(url):
    print("%s正在处理%s" % (threading.current_thread().name, url))
    resp = requests.get(url)
    print("%s获取完成" % url)
    # 方式3 直接调用处理函数  生产和消费者 强行耦合在一起
    # parser_data(resp.text,url)

    return resp.text,url

#消费
def parser_data(f):
    res = f.result()
    print("解析长度为:%s,地址:%s" % (len(res[0]),res[1]))
    print("当前线程:%s" % threading.current_thread().name)

# 要爬取的地址列表
urls = ["https://www.baidu.com/","https://www.bilibili.com","https://www.csdn.net"]

pool = ThreadPoolExecutor()

# fs = []
for url in urls:
    f = pool.submit(get_data,url) #提交任务
    f.add_done_callback(parser_data) # 绑定一个回调函数

    # 方式1 把并发变成了串行
    # data = f.result() # 获取结果
    # parser_data(data) # 解析结果
    # fs.append(f)

# 方式2  必须等待全部完成 不能及时处理
# pool.shutdown()
# for f in fs:
#     data = f.result()
#     parser_data(data[0],data[1])

 

如何为进程或线程绑定回调函数:

 

from threading import Thread
# from multiprocessing import  Process

# def task(callback):
#     print("run......")
#     callback()


# def callback():
#     print("这是回调函数.....")


# if __name__ == '__main__':
#     t = Process(target=task,args=(callback,))
#     t.start()
 

from concurrent.futures import  ProcessPoolExecutor
import os

def task():
    print("task....run")

def call_back(obj):
    print(os.getpid())
    print(obj.result())

if __name__ == '__main__':
    print("主:",os.getpid())
    pool = ProcessPoolExecutor()
    f = pool.submit(task)
    f.add_done_callback(call_back)

线程队列:

# from multiprocessing import Queue

# queue该模块下提供了一些常见的数据容器 但是它们仅仅是容器 每一数据共享这个特点
from queue import Queue,LifoQueue,PriorityQueue

# q = Queue()

# q.put(1)
# q.put(2)
# print(q.get())
# print(q.get())
# print(q.get(timeout=2))

# 后进先出队列(堆栈)
# q = LifoQueue()
# q.put(1)
# # q.put(2)
# # print(q.get())

# 优先级队列
#  需要传入一个元组类型  第一个是优先级 第二是值
# q = PriorityQueue()
# # q.put((-111111,"abc"))
# # q.put((2,"abc"))
# q.put(("a","hello world1"))
# q.put(("A","hello world2"))
# # q.put(("c","hello world"))

# print(q.get())

 

事件:

事件是一个通知信息, 表示什么时间发生了什么事情

用于线程间通讯
线程间 本来就是数据共享的  也就是说 即使没有事件这个东西 也是没问题的

线程之间,执行流程是完全独立的,一些时候可能需要知道另一个线程发生了什么 然后采取一些行动
这时候就可以使用事件来简化代码

事件其实就是帮你维护了一个bool值
在bool为True之前  wait函数将一直阻塞,这样一来就避免了不断的询问对方的状态

假设 有两条线程 一个用于开启服务器  一个用于连接服务器
连接服务器一定要保证 服务器已经启动成功了,服务器启动需要花费一些时间

 

import  time,random
from threading import Thread,Event
# 使用事件
# 一个事件
boot = Event()

def boot_server():
    print("正在启动服务器.......")
    time.sleep(random.randint(2,5))
    print("服务器启动成功.......")
    boot.set()

def connect_server():
    print("开始尝试连接.....")
    boot.wait() # 是一个阻塞函数  会一直等到set()函数被调用
    print("连接服务器成功!")

t1 = Thread(target=boot_server)
t1.start()

t2 = Thread(target=connect_server)
t2.start()

# 不使用事件
# 默认未启动
# is_boot = False

# def boot_server():
#     global is_boot
#     print("正在启动服务器.......")
#     time.sleep(random.randint(2,5))
#     print("服务器启动成功.......")
#     # 修改状态为True
#     is_boot = True


# def connect_server():
#     while True:
#         if is_boot:
#             print("连接服务器成功!")
#             break
#         else:
#             print("连接失败 服务器未启动....")
#             time.sleep(1)
#
# t1 = Thread(target=boot_server)
# t1.start()

# t2 = Thread(target=connect_server)
# t2.start()

 

协程:

协程就是要用单线程实现并发
什么是协程
为什么需要协程
如何使用协程

GIL 导致多个线程不能并行 ,效率低
CPython中多个线程不能并行

既然多个线程也无法提高执行效率  还有没有必须要开线程???
例子: 只有一个岗位 但是有十个任务,请十个人也挺高不了效率,反而增加了系统开销
现在只有一个线程,那要如何并发的处理十个任务

一个线程如何能并发?
    多道技术
    切换+保存状态
    首先任务其实就是一堆代码,一堆代码可以组成一个函数
    如何能使得可以在多个函数之间进行切换

协程指的就是一个线程完成并发执行

在CPython中 如果你的任务是计算密集型  使用协程是无法提高效率的 反而因为切换任务导致效率降低 只能靠进程了
IO密集型 多线程 会比多进程效率高 因为线程的开销比进程小很多

本质上协程还是只有一个线程 所以 一旦遇到IO操作 整个线程就卡主了

协程仅在一下场景能够提高效率
    1.任务是IO密集型
    2.一定要可以检测IO操作 并且在IO即将阻塞时 切换到计算任务  从而使得CPU尽可能多得执行你的线程

协程 指的是一个单线程并发执行多个任务,
用于提高效率 在检测到IO操作时 切换到其他的非IO操作  这样一来 在操作系统眼里程序依然没有阻塞
力求尽可能多的占用CPU执行权

U固定使用场景: IO密集型任务

 

greenlet模块:

greenlet 是对yield进行了封装
简化了书写 但是 它不能检测IO操作

import greenlet
import time

def task1():
    print("task1 run....")
    time.sleep(20)
    g2.switch()
    print("task1 over....")

def task2():
    print("task2 run....")
    print("task2 over....")
    g1.switch()

g1 = greenlet.greenlet(task1)
g2 = greenlet.greenlet(task2)
g1.switch()

 

gevent模块:

greenlet 无法检测IO操作
gevent 即可单线程实现并发 又可以检测IO操作

# import gevent
# import gevent.monkey
# # 打补丁的代码必须放在 导入模块之前
#
#
# import time
# import threading
#
#
# def task1(a,b):
#    print(a,b)
#    # for i in range(1000):
#    print("task1")
#    print(threading.current_thread())
#    time.sleep(1)
#
#
# def task2():
#    # for i in range(1000):
#    print("task2")
#    print(threading.current_thread())
#
#
# g1 = gevent.spawn(task1,123,321)
# g2 = gevent.spawn(task2)
#
# # 协程创建完成后 必须调用join函数  否则任务不会开启  可以理解为线程中的start函数
# # g.start() #
# # g1.join()
# # g2.join()
# gevent.joinall([g1,g2])
# print("over")
 

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

def task():
   s = socket.socket()
   s.bind(("127.0.0.1",8989))
   s.listen()
   while True:
      c,addr = s.accept()
      g2 = gevent.spawn(handler,c)
      # g2.join()

def handler(c):
   while True:
      print(c.recv(1024).decode("utf-8"))

# def task2():
#    while True:
#       time.sleep(1)
#       print("-----------")

g1 = gevent.spawn(task)
g1.join()

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值