tornado入门必备知识总结——异步事件循环与协程

文章目录

    • 前言
    • 同步、异步、阻塞和非阻塞
    • socket的非阻塞io请求html
    • select、poll和epoll
    • 协程
    • 异步http请求
    • tornado实现高并发的爬虫

前言

要想走得远,基础就得牢,路漫漫其修远兮,吾将上下而求索
tornado简介

python web编程三剑客Django,flask,tornado各领风骚。

关于Django和flask的不多说,这里简单介绍下tornado框架。

tornado性能比flask和Django高很多是因为tornado在底层io处理机制上和Django以及flask有着根本的区别:

  1. tornado、gevent、asyncio、aiohttp:底层使用的是事件循环+协程
  2. Django和flask:传统的模型,阻塞io模型

所以当需要使用到高并发时,tornado无疑是最好的选择。

而想要学好tornado框架,就应该先要学好协程与异步事件循环的知识,而不仅仅是学习tornado框架的使用。

tornado优势

  1. 异步编码的一整套方案
  2. tornado不只是web框架,也是web服务器,Django项目也可以使用tornado部署实现高并发
  3. tornado是基于协程的解决方案
  4. tornado提供websocket的长连接(web聊天、消息推送)

tornado是如何做到高并发的

  1. 异步非阻塞io
  2. 基于epoll的事件循环,nginx之所以实现高并发也是基于事件循环
  3. 协程提高了代码的可读性,操作系统的最小操作单元是线程,而协程是比线程更小的操作单元,tornado底层实现了asyncio协程来实现异步非阻塞io

错误理解使用tornado框架

  • 很多人认为tornado提供的只是web框架
  • 只要用tornado就是高并发的
  • tornado中使用了大量的同步io,导致不能发挥异步框架的优势
  • tornado只需要将耗时的操作放到线程池中就可以达到高并发
  • tornado中的多线程和协程的单线程是不是冲突?

尽量使用async和await而不是coroutine装饰器和yield from

  • Python在最开始的时候并没有提供协程,需要使用生成器和coroutine装饰器来实现协程
  • 后来Python3.4实现了async和await关键字来实现协程,相比于生成器的模式更加优雅
  • 基于coroutine是一个从生成器过渡到协程的方案,应当座位历史了解而不是大量使用
  • yield和await的混合使用造成代码的可读性很差
  • 生成器可以模拟协程,但是生成器应该做自己
  • 原生协程总体来说比基于装饰器的协程快
  • 原生协程可以使用async for 和 async with 更pythonic
  • 原生协程返回的是一个awaitable的对象、装饰器的协程返回的是一个future

同步、异步、阻塞和非阻塞

在了解协程前我们需要知道为什么需要协程,得先了解同步、异步、阻塞、非阻塞四个概念,以及造成阻塞的两个主要原因:网络请求io和读取本地文件io,以及如何以非阻塞来解决阻塞问题。

影响服务端性能的因素

  1. cpu的速度远高于io速度,服务端经常阻塞在io读写上导致不能充分使用cpu
  2. IO包括网络访问和本地文件访问,比如requests,urllib等传统的网络库都是同步的io
  3. 网络IO大部分的时间都是处于等待的状态,在等待的时候cpu是空闲的,但是又不能执行其他操作

阻塞、非阻塞

阻塞是指调用函数时候当前线程被挂起。

非阻塞是指调用函数时候当前线程不会被挂起,而是立即返回。

同步、异步

  • 同步是你告诉老婆做饭,然后老婆就开始做饭,你就一直等待饭上桌,但是期间你可以做自己的事,也可以什么都不做。
  • 异步是你告诉老婆去做饭,老婆会立即返回1,你等待饭做好期间可以做自己的事,饭做好后你老婆会通知你,但是通知完后需要你自己去取餐。
  • 同步和异步更关心的是获取结果的方式,同步是获取结果之后才能进行下一步操作。
  • 阻塞和非阻塞更关心的是线程的状态,同步可以调用阻塞,也可以非阻塞。异步是调用非阻塞接口。

socket的非阻塞io请求html

网络阻塞型io请求案例

使用requests库访问请求百度:

import requests
html = requests.get("http://www.baidu.com").text
# 1. 三次握手建立tcp连接
# 2. 等待服务器响应print(html)

在请求服务器的时候,需要经过TCP连接和服务端响应,如果服务端的性能不是很好,就会阻塞浪费很长时间,不能充分利用本地CPU。

编写非阻塞型socket请求

requests库其实就是封装了urllib库,而urllib库的底层使用的就是socket请求,我们可以通过直接编写socket请求,将socket设置为非阻塞,来达到非阻塞的目的:

import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 将socket设置为非阻塞型
client.setblocking(False)host = "www.baidu.com"
try:client.connect((host, 80))
except BlockingIOError as e:# 此时为请求阻塞异常,我们可以在该时间做别的事情passtry:# 因为非阻塞socket,所以可能socket连接可能还没有建立成功,在调用socket方法时需要捕获异常client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format("/",host).encode("utf-8"))
except OSError as e:passdata = b""
while 1:try:# 非阻塞socket,在调用socket方法时需要捕获异常d = client.recv(1024)except BlockingIOError as e:continueif d:data += delse:breakprint(data.decode('utf-8'))

可以发现虽然我们需要手动捕获一些异常,而且这种非阻塞模式并不一定比阻塞模式效率高,但是我们达到了非阻塞的目的,可以在阻塞期间做其他的事情。

select、poll和epoll

上边我们达到了非阻塞的目的,但是我们该如何利用空闲时间呢,有没有一种机制在连接建立好后由操作系统主动通知我们呢,此时就需要用到select、poll、epoll。

select、poll、epoll

  • select,poll,epoll都是IO多路复用的机制。
  • I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
  • 但select, poll, epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的
  • 而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

select

  • select函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds
  • 调用select函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回
  • 当select函数返回后,可以通过遍历fdset, 来找到就绪的描述符
  • select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。
  • select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。

poll

  • 不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。
  • pollfd结构包含了要监视的event和发生的event,不在使用select “参数-值” 传递的方式。同时pollfd没有最大数量限制(但是数量过大后性能也是会下降)。
  • 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符

epoll

  • epoll是select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。
  • epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需要一次

使用selector库达到非阻塞模式

  • selector会根据运行环境自动选择IO多路复用的机制
  • 在使用selector库时需要注册监视文件描述符和传入回调函数参数
import socket
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITEselector = DefaultSelector()class Fetcher:def connected(self, key):"""在写描述符就绪时调用的方法: 发送数据:param key: 写描述符:return:"""# 当事件到达时,我们需要将该描述符移除检测列表selector.unregister(key.fd)# 发送请求连接数据self.client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format("/", self.host).encode("utf-8"))# 发送完毕后,注册监听读描述符,调用读回调函数等待接收数据selector.register(self.client.fileno(), EVENT_READ, self.handle_read)def handle_read(self, key):"""在读描述符就绪时调用的方法:接收数据:param key: 读描述符:return:"""# 接收数据时为阻塞状态d = self.client.recv(1024)if d:self.data += delse:# 当数据接收完毕后将读描述符移除检测列表selector.unregister(key.fd)data = self.data.decode("utf-8")print(data)def get_url(self, url):"""建立套接字连接,注册写文件描述符:param url::return:"""self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 设置非阻塞套接字self.client.setblocking(False)self.host = urlself.data = b""try:# 非阻塞套接字可能还未建立完成,此处需要捕获阻塞异常self.client.connect((self.host, 80))except BlockingIOError as e:pass# 注册监视写描述符,传入回调函数connect用以写入发送数据selector.register(self.client.fileno(), EVENT_WRITE, self.connected)
  • 上边代码虽然实现了非阻塞模式,但是该类还不能运行,需要事件循环驱动才能使代码运行起来。

手动实现事件循环驱动

我们需要实现一个事件循环驱动来使线程运行起来:

def loop_forever():while 1:"""selector.select()方法会根据平台自动选择使用select还是epoll它返回一个(key, events)元组,key是一个namedtuple,可以使用key.name获取元组的数据key的内容(fileobj, fd, events, data):fileobj 已经注册的文件对象fd      也就是第一个参数的那个文件对象的更底层的文件描述符events  等待IO事件data    可选项。可以存一些和fileobj有关的数据,如session的id"""ready = selector.select()   # 检测有无活动对象,没有就阻塞在这里等待for key, mask in ready:     # 有活动对象了call_back = key.data    # key.data 是注册时传递的回调函数名称call_back(key)          # 回调

完整代码如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @File  : select_test.py
# @Author: itnoobzzy
# @Date  : 2021/2/22
# @Desc  :import socket
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITEselector = DefaultSelector()class Fetcher:def connected(self, key):"""在写描述符就绪时调用的方法: 发送数据:param key: 写描述符:return:"""# 当事件到达时,我们需要将该描述符移除检测列表selector.unregister(key.fd)# 发送请求连接数据self.client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format("/", self.host).encode("utf-8"))# 发送完毕后,注册监听读描述符,调用读回调函数等待接收数据selector.register(self.client.fileno(), EVENT_READ, self.handle_read)def handle_read(self, key):"""在读描述符就绪时调用的方法:接收数据:param key: 读描述符:return:"""# 接收数据时为阻塞状态d = self.client.recv(1024)if d:self.data += delse:# 当数据接收完毕后将读描述符移除检测列表selector.unregister(key.fd)data = self.data.decode("utf-8")print(data)def get_url(self, url):"""建立套接字连接,注册写文件描述符:param url::return:"""self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 设置非阻塞套接字self.client.setblocking(False)self.host = urlself.data = b""try:# 非阻塞套接字可能还未建立完成,此处需要捕获阻塞异常self.client.connect((self.host, 80))except BlockingIOError as e:pass# 注册监视写描述符,传入回调函数connect用以写入发送数据selector.register(self.client.fileno(), EVENT_WRITE, self.connected)def loop_forever():while 1:"""selector.select()方法会根据平台自动选择使用select还是epoll它返回一个(key, events)元组,key是一个namedtuple,可以使用key.name获取元组的数据key的内容(fileobj, fd, events, data):fileobj 已经注册的文件对象fd      也就是第一个参数的那个文件对象的更底层的文件描述符events  等待IO事件data    可选项。可以存一些和fileobj有关的数据,如session的id"""ready = selector.select()   # 检测有无活动对象,没有就阻塞在这里等待for key, mask in ready:     # 有活动对象了call_back = key.data    # key.data 是注册时传递的回调函数名称call_back(key)          # 回调if __name__ == '__main__':fetcher = Fetcher()url = "http://wwww.baidu.com"fetcher.get_url(url)loop_forever()

tornado底层的实现就是异步事件循环驱动,只有先理解了事件循环驱动的原理和协程的原理才能真正理解tornado框架,灵活使用tornado框架

协程

上边所说的事件循环驱动的代码是同步的代码,而且是比较简单的回调较少的代码,我们可以明显的看出这种回调代码的可读性很差,一旦回调栈过深就会使代码难以维护,而且栈撕裂造成异常无法向上抛出:比如上边的handle_read函数如果出异常了,该异常并不会层层向上抛出。

此时就出现了使用协程来解决同步的问题,达到异步事件循环驱动

协程:可以被暂停切换到其他协程运行的函数

如果对协程与生成器之间关系不了解可以查看我以前写的这篇文章Python中的协程。

把生成器变为协程

  • 将生成器变为协程有两种方法:

    1. 使用coroutine装饰器:

      from tornado.gen import coroutine@coroutine
      def yield_test():yield 1yield 2yield 3
      
    2. 使用async关键字:

      from tornado.gen import coroutine@coroutine
      def yield_test():yield 1yield 2yield 3async def main():await yield_test()
      
  • 需要注意的是yield from只能在coroutine装饰的协程中使用而不能在使用async 定义的协程中使用:
    在这里插入图片描述

    async定义的协程可以使用await关键字:
    在这里插入图片描述

  • 但是更推荐使用async来定义协程,coroutine是Python定义协程历史遗留之物,async在是未来大势所趋,而且tornado底层就已经使用async定义协程。

使用协程切换调度

当遇到耗时的IO操作,我们可以将这些操作抽离为单独的协程函数,然后再使用await关键字切换调度,这样就可以达到异步地目的。

import time
import asyncio# 这个库的作用是将异步生成器返回值变为可迭代对象
from aiostream.stream import list as alistasync def yield_test():time.sleep(3)yield 1async def yield_test2():time.sleep(3)yield 2async def main():# 这里如果不使用alist转换会报错TypeError: ‘async_generator‘ object is not iterableresult1 = await alist(yield_test())    # 调度yield_test协程获取结果1result2 = await alist(yield_test2())   # 调度yield_test2协程获取结果2print(result1, result2)if __name__ == '__main__':loop = asyncio.get_event_loop()loop.run_until_complete(main())

异步http请求

传统的http请求库requests库和urllib库都是同步库,tornado提供了异步http请求方法:httpclient

from tornado import httpclient, ioloopasync def f():http_client = httpclient.AsyncHTTPClient()try:response = await http_client.fetch("http://www.baidu.com")except Exception as e:print("Error: %s" % e)else:print(response.body.decode('utf-8'))if __name__ == '__main__':# # tornado自身的事件循环驱动# io_loop = ioloop.IOLoop.current()# io_loop.run_sync(f)# 我们也可以使用内置的asyncio实现事件循环驱动import asyncioasyncio.ensure_future(f())asyncio.get_event_loop().run_forever()

tornado实现高并发的爬虫

根据上边的异步http请求和协程实现高并发的爬虫:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @File  : tornado_spider.py
# @Author: itnoobzzy
# @Date  : 2021/2/22
# @Desc  : 高并发异步爬虫from urllib.parse import urljoinfrom bs4 import BeautifulSoup
from tornado import gen, httpclient, queues, ioloopbase_url = "http://www.tornadoweb.org/en/stable/"
concurrency = 3async def get_url_links(url):# 使用异步http请求response = await httpclient.AsyncHTTPClient().fetch(base_url)html = response.body.decode("utf-8")soup = BeautifulSoup(html)# 获取页面上的所有连接links = [urljoin(base_url, a.get("href")) for a in soup.find_all("a", href=True)]return linksasync def main():# 防止重复爬取,将爬取过的链接放入集合中seen_set = set()# 这里使用的是tornado自身的队列,该队列为异步队列,内部实现了__aiter__和__anext__方法q = queues.Queue()async def fetch_url(current_url):"""生产者:异步调用get_url_links获取当前页面上的上的所有链接:param current_url::return:"""# 生产者退出条件:说明该链接爬过,退出if current_url in seen_set:returnprint("获取{}".format(current_url))# 将获取的链接放入集合中,和放入队列中seen_set.add(current_url)next_urls = await get_url_links(current_url)for new_url in next_urls:# 判断是否base_url开头是为了防止爬取外站的链接if new_url.startswith(base_url):# 这里使用await 放入队列是因为防止队列放满会产生异常导致阻塞await q.put(new_url)async def worker():"""消费者:异步调用生产者爬取链接:return:"""# 因为该queue实现了__aiter__方法所以可以使用async for 进行迭代async for url in q:# 消费者退出条件:可以通过在队列的最后加入None来判断结束退出if url is None:returntry:await fetch_url(url)except Exception as e:print('exception')finally:q.task_done()# 放入初始url到队列await q.put(base_url)# 启动协程: 创建三个消费者workers = gen.multi([worker() for _ in range(concurrency)])# 等待队列加入完毕await q.join()# 这里加入三个None是因为有三个消费者协程需要退出for _ in range(concurrency):await q.put(None)#  调用workersawait workersif __name__ == '__main__':io_loop = ioloop.IOLoop.current()io_loop.run_sync(main)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1621499.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【Tornado】Tornado入门教程

目录 Tornado特点结构三个底层核心模块 安装1. 安装python32. 安装tornado3. 编写简单server4. 运行流程 核心组件1. ioloop实例2. app实例3. urls路由表4. handler类 异步协程async 和 await如何工作的怎样调用 协程模式结合 callbacks调用阻塞函数交叉存取技术循环在后台运行…

【数据结构与算法篇】手撕八大排序算法之交换排序

​👻内容专栏: 《数据结构与算法篇》 🐨本文概括:常见交换排序包括冒泡排序与快速排序,本篇讲述冒泡排序与快速排序的思想及实现、复杂度分析。 🐼本文作者: 花 蝶 🐸发布时间&#…

ie ajax十分卡,解决jquery .ajax 在IE下卡死问题的解决方法

解决jquery .ajax 在IE下卡死问题的解决方法 解决IE编码问题第一步: dataType:($.browser.msie) ? "text" : "xml" 先这样做让IE 识别返回的是text 还是xml 第二步: 复制代码 代码如下: function parseXml(xml) { //XML IE编码问题…

数学分析:场论

我们之前知道的是里斯表示定理。 这里看到,对于多重线性映射,里斯表示定理会从内积变成混合积。当然我们还是只考虑三维以内的情况。 于是我们可以把不同的1形式和2形式的下标写上,表示他们相当于内积或者混合积对应的那个向量。 然后还差0形…

设计师:设计师之家装材料知识之家装八项(吊顶材料、门窗材料、五金材料、墙面材料、地面材料、胶粘材料、油漆材料、水电材料等)之详细攻略

设计师:设计师之家装材料知识之家装八项(吊顶材料、门窗材料、五金材料、墙面材料、地面材料、胶粘材料、油漆材料、水电材料等)之详细攻略 目录 家装八项 吊顶材料 门窗材料 五金材料 墙面材料 地面材料 胶粘材料 油漆材料 水电材料 参考文章:…

材料封样信息流程指引

材料封样信息流程指引 一、材料封样流程 1、新项目中标后,由设计院根据合同、招标文件、技术要求填写材料送样清单(格式按公司标准化标格填写),并正式下发给采购部、工程管理部及项目部安排后续送样确认; 2、幕墙工程…

EyouCMS响应式木材板材公司模板/易优CMS装修材料类企业网站模板

☑️ 编号:ym247 ☑️ 品牌:EyouCMS ☑️ 语言:PHP ☑️ 大小:22.4MB ☑️ 类型:木材板材公司模板 ☑️ 支持:pcwap 🎉 欢迎免费领取(注明编号) 🎉 ✨ 源码介…

2023最新装修材料石膏线品牌加盟类模板源码+织梦内核开发的

正文: 装修材料石膏线品牌加盟类织梦模板,带手机版数据同步。 效果相当的炫酷,相当简洁大气高端。适用于建材网站源码、石英石网站模版,有兴趣的自行去安装体验吧,其它就没什么好介绍的了。 程序: wweohd.lanzouo.com/iRCs80t…

Linux内核学习(十一)—— 进程地址空间(基于Linux 2.6内核)

目录 一、地址空间 二、内存描述符 三、虚拟内存区域 四、操作内存区域 find_vma() mmap() 和 do_mmap():创建地址区间 五、页表 一、地址空间 进程地址空间由进程可寻址并且允许进程使用的虚拟内存组成, 每个进程都有一个 32 位或 64 位的平坦&…

在云原生环境中构建可扩展的大数据平台:方法和策略

文章目录 1. **选择适当的云提供商:**2. **采用容器化和微服务架构:**3. **分层架构设计:**4. **弹性计算资源:**5. **使用分布式计算框架:**6. **数据分区和分片:**7. **使用列式存储:**8. **缓…

java 高级面试题整理(薄弱技术-2023)

session 和cookie的区别和联系 session1.什么是session Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就…

第一讲:工业网络的“语言”——通讯标准

工业网络的“语言”——通讯标准 a) CC-Link CANopen Modbus等; b) 民用网络:TCP-IP协议 c) 民用网络VS工业网络——网络架构: i. 民用网络架构: ii. 工业网络架构: 从网络架构上来看&#xff…

时间敏感网络(TSN)关键协议的介绍

TSN的概述 为了简洁明了,此笔记不再介绍TSN的背景知识。 由于通信主体的演进,各个业务对于时间敏感程度愈加严格。为了构建一个统一的数据链路层协议,通过标准化使其在不同的领域都可以同构运行,提供实时数据的传输保障。 时间…

Arduino ESP32 最简单直接获取网络时间方法

Arduino ESP32 最简单直接获取网络时间方法 ✨在 ArduinoESP32核心支持库当中已经包含相关的获取时间的库,所有获取网络时间,只需要连接好网络,调用相关的库函数即可实现NTP时间的获取,免去的额外加载扩展库的头文件。 &#x1f9…

TCN-时间卷积网络

目录 一、引言 二、时序卷积神经网络 2.1 因果卷积(Causal Convolution) 2.2 膨胀卷积(Dilated Convolution) 2.3 残差链接(Residual Connections) 三、讨论和总结 1. TCN的优点 2. TCN的缺点 参考…

DBeaver的安装和使用:windows版

DBeaver官网下载地址:https://dbeaver.io/download/ 下载完成后, 进入傻瓜式安装: 这里会进入重复界面,一样点击下一步即可 选择安装目录,尽量不要选C盘, 我的电脑只有c盘, 没办法 等待安装完成…

这款远程桌面软件开源了

相信在七八年前,大部分读者都使用 QQ 远程控制电脑。到后面,才接触到一些好用的远程控制产品,比如 Teamviewer、向日葵等。 最近,自己装的远程控制产品试用期到了,便想到去 GitHub 找找有没有可以替代的开源项目&#…

Modbus转Profinet网关在大型自动化仓储项目应用案例

在自动化仓储项目中,Modbus是一种常见的通信协议,用于连接各种设备,例如传感器、PLC和人机界面。然而,Modbus协议只支持串行通信,并且数据传输速度较慢。为了提高通信效率和整体系统性能,许多大型仓储项目选…

LeetCode-455-分发饼干-贪心算法

题目描述: 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j&#xff…

手机怎么解决同ip多账号_游戏工作室如何实现手游多开多窗口多IP

经常能看到的一个画面就是游戏工作室,一台电脑许多个手机游戏窗口同时进行,需求量1台程序运行好几个微端。或是相同应用程序开启好几个窗口。那样做能够节约成本,不用多个设备。 但他们全是公用相同网络ip地扯得,那麼如何来防止由…