用 Python 理解 Web 并发模型

http://www.jianshu.com/users/1b1fde012122/latest_articles

 

来源:MountainKing

链接:http://www.jianshu.com/p/80feb3bf5c70#

前言

虽然异步是我们急需掌握的高阶技术,但是不积跬步无以至千里,同步技术的学习是不能省略的。今天这篇文章主要用Python来介绍Web并发模型,直观地展现同步技术的缺陷以及异步好在哪里。

最简单的并发

import socket

response = 'HTTP/1.1 200 OK\r\nConnection: Close\r\nContent-Length: 11\r\n\r\nHello World'

server = socket.socket()

server.bind(('0.0.0.0', 9527))

server.listen(1024)

while True:

client, clientaddr = server.accept()  # blocking

request = client.recv(1024)  # blocking

client.send(response)  # maybe blocking

client.close()

上面这个例子太简单了,访问localhost:9527,返回“Hello World”。用ab来测试性能,数据如下:

ab -n 100000 -c 8 http://localhost:9527/

Time taken for tests:   1.568 seconds

发送10万个请求,8(我的CPU核数为8)个请求同时并发,耗时1.568秒。

性能瓶颈在哪里呢?就在上面的两个半阻塞。

accept和recv是完全阻塞的,而为什么send是半个阻塞呢?

在内核的 socket实现中,会有两个缓存 (buffer)。read buffer 和 write buffer 。当内核接收到网卡传来的客户端数据后,把数据复制到 read buffer ,这个时候 recv阻塞的进程就可以被唤醒。

当调用 send的时候,内核只是把 send的数据复制到 write buffer 里,然后立即返回。只有 write buffer 的空间不够时 send才会被阻塞,需要等待网卡发送数据腾空 write buffer 。在 write buffer的空间足够放下 send的数据时进程才可以被唤醒。

如果一个请求处理地很慢,其他请求只能排队,那么并发量肯定会受到影响。

多进程

每个请求对应一个进程倒是能解决上面的问题,但是进程太占资源,每个请求的资源都是独立的,无法共享,而且进程的上下文切换成本也很高。

import socket

import signal

import multiprocessing

response = 'HTTP/1.1 200 OK\r\nConnection: Close\r\nContent-Length: 11\r\n\r\nHello World'

server = socket.socket()

server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server.bind(('0.0.0.0', 9527))

server.listen(1024)

def handler(client):

request = client.recv(1024)

client.send(response)

client.close()

#多进程里的子进程执行完后并不会死掉,而是变成僵尸进程,等待主进程挂掉后才会死掉,下面这条语句可以解决这个问题。

signal.signal(signal.SIGCHLD,signal.SIG_IGN)

while True:

client, addr = server.accept()

process = multiprocessing.Process(target=handler, args=(client,))

process.start()

Prefork

这是多进程的改良版,预先分配好和CPU核数一样的进程数,可以控制资源占用,高效处理请求。

import socket

import multiprocessing

response = 'HTTP/1.1 200 OK\r\nConnection: Close\r\nContent-Length: 11\r\n\r\nHello World'

server = socket.socket()

server.bind(('0.0.0.0', 9527))

server.listen(1024)

def handler():

while True:

client, addr = server.accept()

request = client.recv(1024)

client.send(response)

client.close()

processors = 8

for i in range(0, processors):

process = multiprocessing.Process(target=handler, args=())

process.start()

耗时:1.640秒。

线程池

import Queue

import socket

import threading

response = 'HTTP/1.1 200 OK\r\nConnection: Close\r\nContent-Length: 11\r\n\r\nHello World'

server = socket.socket()

server.bind(('0.0.0.0', 9527))

server.listen(1024)

def handler(queue):

while True:

client  = queue.get()

request = client.recv(1024)

client.send(response)

client.close()

queue = Queue.Queue()

processors = 8

for i in range(0, processors):

thread = threading.Thread(target=handler, args=(queue,))

thread.daemon = True

thread.start()

while True:

client, clientaddr = server.accept()

queue.put(client)

耗时:3.901秒,大部分时间花在队列上,线程占用资源比进程少(资源可以共享),但是要考虑线程安全问题和锁的性能,而且python有臭名昭著的GIL,导致不能有效利用多核CPU。

epoll

import select

import socket

response = 'HTTP/1.1 200 OK\r\nConnection: Close\r\nContent-Length: 11\r\n\r\nHello World'

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.setblocking(False)

server_address = ('localhost', 9527)

server.bind(server_address)

server.listen(1024)

READ_ONLY = select.EPOLLIN | select.EPOLLPRI

epoll = select.epoll()

epoll.register(server, READ_ONLY)

timeout = 60

fd_to_socket = { server.fileno(): server}

while True:

events = epoll.poll(timeout)

for fd, flag in events:

sock = fd_to_socket[fd]

if flag & READ_ONLY:

if sock is server:

conn, client_address = sock.accept()

conn.setblocking(False)

fd_to_socket[conn.fileno()] = conn

epoll.register(conn, READ_ONLY)

else:

request = sock.recv(1024)

sock.send(response)

sock.close()

del fd_to_socket[fd]

最后祭出epoll大神,三大异步通信框架Netty、NodeJS、Tornado共同采用的通信技术,耗时1.582秒,但是要注意是单进程单线程哦。epoll真正发挥作用是在长连接应用里,单线程处理上万个长连接玩一样,占用资源极少。

用 Python 理解 Web 并发模型的更多相关文章

  1. 用python理解web并发模型

    最简单的并发 import socket response = 'HTTP/1.1 200 OK\r\nConnection:Close\r\nContent-Length:11\r\n\r\nHel ...

  2. web并发模型

    并发:cpu划分时间片,轮流执行每个请求任务,时间片到期后,换到下一个. 并行:在多核服务器上,每个cpu内核执行一个任务,是真正的并行 IO密集型的应用,由于请求过程中很多时间都是外部IO操作,CP ...

  3. 共享内存 & Actor并发模型哪个更快?

    HI,前几天被.NET圈纪检委@懒得勤快问到共享内存和Actor并发模型哪个速度更快. 前文传送门: 说实在,我内心10w头羊驼跑过...... 先说结论 首先两者对于并发的风格模型不一样. 共享内存 ...

  4. 【并发编程】一文带你读懂深入理解Java内存模型(面试必备)

    并发编程这一块内容,是高级资深工程师必备知识点,25K起如果不懂并发编程,那基本到顶.但是并发编程内容庞杂,如何系统学习?本专题将会系统讲解并发编程的所有知识点,包括但不限于: 线程通信机制,深入JM ...

  5. Java并发指南2:深入理解Java内存模型JMM

    本文转载自互联网,侵删   一:JMM基础与happens-before 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实 ...

  6. python三大web框架Django,Flask,Flask,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架

    Python几种主流框架 从GitHub中整理出的15个最受欢迎的Python开源框架.这些框架包括事件I/O,OLAP,Web开发,高性能网络通信,测试,爬虫等. Django: Python We ...

  7. 理解Storm并发

    作者:Jack47 PS:如果喜欢我写的文章,欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源. 注:本文主要内容翻译自understanding-the-parall ...

  8. Server Develop (五) Linux并发模型

    Linux并发模型 目前可以实现并发程序的方法有Apache模型(Process Per Connection,简称PPC),TPC(Thread PerConnection)模型,以及select模 ...

  9. Linux并发模型

    Linux并发模型 Linux并发模型 目前可以实现并发程序的方法有Apache模型(Process Per Connection,简称PPC),TPC(Thread PerConnection)模型 ...

随机推荐

  1. 【BZOJ 1602】 牧场行走

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1602 [算法] 倍增求LCA [代码] #include<bits/stdc+ ...

  2. 洛谷P3809 后缀数组模板

    题目:https://www.luogu.org/problemnew/show/P3809 刚学了后缀数组,看人家手写演示了半天,大概明白了过程,但完全写不出来代码: 于是借鉴了许多,不过都差不多, ...

  3. bzoj3957

    数学+模拟 细节很多 首先我们发现,如果两个区间已经包含,那么可以输出empty,一个数能通过变换得到另一个区间的数,这个区间的大小必须小于等于终点区间的大小.加法不会改变区间大小,只有乘法会改变,而 ...

  4. 树莓派(raspberry)启用root账户

    树莓派使用的linux是debian系统,所以树莓派启用root和debian是相同的. debian里root账户默认没有密码,但账户锁定. 当需要root权限时,由默认账户经由sudo执行,Ras ...

  5. Nightmare --- 炸弹时间复位

    题目大意: 该题为走迷宫,其条件有如下6个: 1, 迷宫用二维数组来表示: 2, 人走动时不能越界,不能在墙上走: 3, 当走到出口时,若剩余时间恰好为0,则失败: 4, 找到炸弹复位装置,若剩余时间 ...

  6. 【贪心】小Y的炮[cannon]题解

    模拟赛的题目,做的时候由于第二题表打太久了,只剩下40分钟,想都没想就写了一个爆搜20分... 这道题单调性很关键,下面会解释 P.S.解释在代码里 #include<cstdio> #i ...

  7. CSS的常用属性(二)

    盒子模型之边框 border-(top/bottom/left/right)-style: solid 边框的风格 如(solid 实线,dotted 点线,dashed 虚线) border-top ...

  8. Django学习案例一(blog):二. 连接数据库

    本例使用了django默认的sqlite3数据库,配置文件不需要作调整: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite ...

  9. 3B课程笔记分享_StudyJams_2017

    昨晚才发现 Study Jams China的官方论坛也支持MarkDown,所以直接发在了那上面.http://www.studyjamscn.com/thread-21807-1-1.html

  10. Android 解析XML—pull解析方式

    在Android中,常见的XML解析器分别为SAX解析器.DOM解析器和PULL解析器,其中PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部 ...