异步编程——阻塞式I / O和非阻塞式I / O

翻译自 Asynchronous programming. Blocking I/O and non-blocking I/O,阅读需要9分钟

这是关于异步编程的系列文章的第一篇文章。整个系列试图回答一个简单的问题:“什么是异步?”。

起初,当我刚开始深入研究这个问题时,我以为我知道它是什么。但是事实是,我甚至没找到异步是什么的线索。所以,让我们找出来!

全系列:

在这篇文章中,我们将讨论网络,但您可以轻松地将其映射到其他输入/输出(I / O)操作,例如,将Socket(下文将译作套接字)更改为文件描述符。尽管示例将使用Python,不过这并不是专注于任何特定编程语言的讲解(我想说 – 我喜欢Python)。

在客户端 – 服务器应用程序中,当客户端向服务器发出请求时,服务器处理请求并发回响应。为此,客户端和服务器首先需要建立彼此的连接,这就是套接字发挥作用的地方。最后客户端和服务器都必须将自己绑定到一个套接字,服务器开始监听其套接字以便客户端发出请求。

如果你看一下处理器处理的速度和网络连接速度的比例,会发现差异有两个数量级。事实证明,如果我们的应用程序使用I / O,那么CPU在大多数情况下都不会执行任何操作,这种类型的应用程序称为I / O密集型。对于需要高性能的应用程序,这是一个主要的障碍,因为其他活动和其他I / O操作都在等待 ——事实证明这些系统都是低效的。

组织I / O的方式有3个选项:阻塞式,非阻塞式和异步式。最后一个不适用于网络,因此,我们有两个选项 – 阻止式和非阻塞式。

阻止式I / O

使用阻塞I / O,当客户端发出连接到服务器的请求时,处理该连接的套接字将被阻塞,直到有一些数据要读取或数据被完全写入。在操作完成之前,服务器除了等待之外别无他法。由此得出最简单的结论:在单个执行线程中,我们不能提供多个连接。默认情况下,TCP套接字处于阻塞模式。

在UNIX(POSIX)BSD套接字的例子中考虑这个选项(在Windows中都是相同的 – 调用方式不一样,但逻辑是相同的)。

关于Python的简单示例,客户端代码:

和服务器代码:

您会注意到服务器持续打印我们的消息“”,直到所有数据都被发送。在上面的代码中,“Data Received”消息将不会被打印,因为客户端必须发送大量数据,这需要花费时间,并且在此之前套接字会被阻塞。

这里发生了什么?该send()方法尝试将所有数据传输到服务器,而客户端上的写缓冲区将继续获取数据。当缓冲区变空时,内核将再次唤醒进程以获取要传输的下一个数据块。简而言之,您的代码将被阻塞,并且不会让任何其他事务继续进行。

如果要用这种方法来实现并发请求,我们就需要有多个线程,即我们需要为每个客户端连接分配一个新线程。我们一会儿会讨论这个问题。

非阻塞式I / O

还有第二个方案- 非阻塞I / O。从措辞来看,差异是显而易见的 – 不是 – 阻塞式的,从客户端角度来看,任何操作都会立即完成。非阻塞式I / O意味着请求立即排队,函数返回。然后在稍后的某个时刻处理实际的I / O。

回到我们的示例,在客户端进行一些更改:

现在,如果我们运行此代码,您会注意到程序将运行一小段时间,它将打印“数据已发送”并终止。

这里发生了什么?这里客户端没有发送完所有数据。当我们通过使用非阻塞式套接字调用setblocking(0)时,它永远不会等待操作完成。因此,当我们调用该send()方法时,它会将尽可能多的数据放入缓冲区并返回。

使用此选项,我们可以同时从一个线程对不同的套接字执行多个I / O操作。但是,由于不知道套接字是否已准备好进行I / O操作,我们必须询问每个套接字,最后就会陷入无限循环。

为了摆脱这种效率低下的循环,需要一种轮询准备机制,我们可以在这里轮询所有套接字的准备情况,并告诉我们哪些套接字已准备好进行新的I / O操作。当任一套接字准备就绪时,我们将执行排队操作,之后我们可以返回阻塞状态,等待再次为下一个I / O操作做好准备的套接字。

轮询准备有几种机制,它们在性能和细节方面有所不同,但通常情况下,细节隐藏在“引擎盖下”并且对我们来说是不可见的。

要搜索的关键字:

消息通知:

  • Level Triggering(state)(译者注:条件触发,状态不改变时)
  • Edge Triggering(state changed)(译者注:边缘触发,状态改变时)

机制:

  • select(), poll()
  • epoll(), kqueue()
  • EAGAIN, EWOULDBLOCK

多任务处理

我们的目标是同时管理好多个客户端,如何该如何确保多个请求的同时处理呢?有几种选择:

独立进程

最简单的也是历史上最初的方法是在单独的进程中处理每个请求。这很好,因为我们可以使用相同的阻塞I / O API。如果进程突然崩溃,它只会影响在这个特定进程中处理的操作,而不会影响其他任何进程。

减少——通信困难。比如,在形式上,进程之间几乎没有任何共同点,我们想要组织的任何非特殊的通信都需要额外的努力来同步访问。此外,在任何给定的时间点,可能有多个进程在等待客户端请求,这只是浪费资源。

让我们来看看这在实践中是如何工作的:通常第一个进程(主进程/守护进程)启动,例如,监听,然后它产生一些进程作为工作进程,每个进程可以在同一个套接字上接受等待传入的连接。一旦传入连接出现,其中一个进程被占用 – 它接收此连接,从开始到结束处理,关闭套接字,并再次准备好完成下一个请求。可能的变化——可以为每个传入连接生成过程,或者它们都是预先启动的,等等。这可能会影响性能特征,但现在对我们来说并不那么重要。

此类系统的示例:

  • Apache的mod_prefork;
  • 针对经常使用PHP的人的FastCGI;
  • 针对在Ruby on Rails上写作的人的Phusion Passenger;
  • PostgreSQL。

线程(OS)

另一种方法是使用操作系统(OS)线程。在一个进程中,我们生成多个线程。也可以使用阻塞式I / O,因为只会阻塞一个线程。操作系统自身管理线程,它能够在处理器之间分散它们。线程比进程轻量级。实质上,这意味着我们可以在同一系统上生成更多线程。我们很难运行一万个进程,但是一万个线程很容易。并不是说它会有效,但是,它们更轻巧。

另一方面,没有隔离,即如果发生某种崩溃,它不仅会崩溃一个特定的线程而且会崩溃整个进程。最大的困难是线程和正在工作的进程里的其他线程之间存在共享。我们共享资源——内存,这意味着需要同步访问。同步访问共享内存的问题——这是最简单的情况,但是,例如,可能存在与数据库的连接,或者与数据库的连接池,这对于处理即将到来的请求的应用程序内的所有线程很常见。要正确处理对资源的同步访问是很难的。

有一些问题:

  1. 首先——在同步过程中可能出现死锁。当进程或线程进入等待状态时发生死锁,因为请求的系统资源由另一个等待进程保持,而另一个等待进程又等待另一个等待进程持有的另一个资源;
  2. 当我们对共享数据具有竞争性访问权限时,会发生不充分的同步。粗略地说,两个线程同时更改数据并破坏它们。这样的程序更难调试,并非所有错误都立即出现。例如,著名的GIL – Global Interpreter Lock – 是制作多线程应用程序的最简单方法之一。当使用GIL时我们说所有的数据结构,我们所有的内存都只受到整个进程的一个锁的保护。这似乎意味着多线程执行是不可能的,因为只能执行一个线程,只有一个锁,有人已经捕获它,所有其他线程都无法工作。是的,这是事实,但请记住,大多数情况下我们不对线程进行任何计算,而是进行网络I / O操作,因此在访问阻塞I / O操作时,GIL会关闭,线程会重置并且实际上会切换到另一个准备执行的线程。因此,从后端的角度来看,使用GIL可能并不是那么糟糕。

总结

阻塞方法同步执行——运行应用程序,它的操作在调用后直接执行。

非阻塞方法异步执行——您运行应用程序并且非阻塞操作立即返回,但实际工作是在稍后执行。

实现多任务处理有几种方法:线程和进程。

在下一篇文章中,我们将讨论合作多任务及其实现。

翻译参考:

  1. 边缘触发和条件触发
  2. 触发中断

python 使用函数名的字符串调用函数(4种方法)

先看一个例子:

我们希望遍历执行列表中的函数,但是从列表中获得的函数名是字符串,所以会提示类型错误,字符串对象是不可以调用的。如果我们想要字符串变成可调用的对象呢?或是想通过变量调用模块的属性和类的属性呢?以下有三种方法可以实现。

eval()

eval() 通常用来执行一个字符串表达式,并返回表达式的值。在这里它将字符串转换成对应的函数。eval() 功能强大但是比较危险(eval is evil),不建议使用。

locals()和globals()

locals() 和 globals() 是python的两个内置函数,通过它们可以一字典的方式访问局部和全局变量。

getattr()

getattr() 是 python 的内建函数,getattr(object,name) 就相当于 object.name,但是这里 name 可以为变量。

返回 foo 模块的 bar 方法

返回 Foo 类的属性

标准库operator下的methodcaller函数

参考

Calling a function of a module from a string with the function’s name in Python

How do I use strings to call functions/methods?

https://blog.csdn.net/u013679490/article/details/54767170

SQLAlchemy 执行语句的打印

最近需要分析业务代码中MySQL语句执行效率,对于直接显示执行SQL语句的地方,可以直接将SQL语句和参数拼接后的结果打印出来,但是对于用SQLAlchemy Query查询的地方,就需要想办法打印出最后实际执行的语句。找了下各种方案,最后结果如下:

方式一:

SQLAlchemy支持在创建数据库引擎时,通过echo=true,将连接这个数据库引擎的所有执行语句打印出来:

官网说明: http://docs.sqlalchemy.org/en/latest/core/engines.html?highlight=echo#sqlalchemy.create_engine.params.echo

这种方式会自动将连接数据库过程中的所有类型语句,以及这些语句的参数列表打印出来。也即是说,这种方式打印出的语句和语句中的参数是分离的。

方式二:

对于某一个像上边这样的查询或插入语句,可以直接通过下边的方式,打印出不包含参数的SQL语句

这种方式得到的SQL也不可直接执行,因为对应的参数变量没有被替换

方式三

这种方式可以打印包含参数的执行语句,但是参数只包括数字和字符串等基本类型。其中dialects表示需要的数据库方言,我这里用的mysql。

基于方式三,增加部分代码,来打印datetime等类型的参数。

上边的代码支持 SQA 1.0.13及以上版本,支持select、insert和update命令。对其他版本的情况,可以参考

https://stackoverflow.com/questions/30992092/sqlalchemy-how-to-get-raw-sql-from-insert-update-statements-with-binded-p

参考:

http://stackoverflow.com/questions/5631078/sqlalchemy-print-the-actual-query

https://gist.github.com/gsakkis/4572159

grep -c 统计文件中字符串出现的行数

分析日志时,有时会需要统计某个方法出现的次数,这时就要用到grep -c,如下命令

grep -c  key_word  file1 file2 file3

返回

file1:count1

file2:count2

file3:count3

即依次返回每个文件中key_word出现的行数

与之类似的

grep key_word  file1 file2 file3 |wc -l

返回

count1 + count2 + count3

即返回的是多个文件中key_word出现的总行数

注意是行数不是次数,比如一个文件中只有一行,即使要查询的字符串出现了很多次,也只会打印1

Mysql like查询语句中,结果包含反斜杠 \ 字符的,需要替换成四个反斜杠 \\\\

    如题,当SQL语句中使用Like查询,且期望匹配的结果中含有”\”的,应当把”\”替换为”\\\\”。

    比如数据库中text字段有以下三行:

当我们使用下面这三种方式查询时,都会得到id 为1,2,3三行,原因后面会讲。

只有像下面这样使用四个反斜杠”\\\\”查询时,才会得到期望的包含”\24″的结果(id为2、3的两行)。

进一步,如果期望查询到的结果更准确,比如只得到id为2的第二行,应该像下边这样查询。

同理,只得到id为3的第三行,匹配两个反斜杠”\\”,应该使用八个反斜杠”\\\\\\\\”:

原因其实很简单,在mysql中,反斜杠在字符串中是转义字符,在进行语法解析时会进行一次转义,所以当我们在insert字符时,insert “\\” 在数据库中最终只会存储”\”。而在mysql的like语法中,like后边的字符串除了会在语法解析时转义一次外,还会在正则匹配时进行第二次的转义。因此如果期望最终匹配到”\”,就要反转义两次,也即由”\”到”\\”再到”\\\\”。

Hello world

你好世界!

こんにちは世界

Ciao mondo

Привет мир

Bonjour le monde

안녕 세상

Hallo Welt

Olá mundo

Hola Mundo

Γειά σου Κόσμε

Salut Lume

สวัสดีชาวโลก

Hej världen

salve mundi

Hai dunia

 

神毁灭了一座巴别塔,也许我们可以用计算机再建造一座。

God destroyed the Tower of Babel. Maybe we can build another one with the computer.