gopython 多线程问题_Django 多线程问题是怎么回事?

简单的说就是服务端监听 socket 每次 accept 一个新的请求后,开一个线程处理 这个 socket 客户连接。如果你对底层实现原理感兴趣,可以继续看下去,从 socket 编程的角度来解释多线程 wsgi server。最后附上一个异步框架工作过程的视频讲解。

这里我们自己撸一个简单的多线程 wsgi server 来看下原理,还是需要深入源码和 socket 编程你才能真正理解。 我们从 python 自带的一个 wsgi server 看下如何实现多线程处理请求。首先你需要熟悉下 wsgi。 看一个最简单的 wsgi app:

#!/usr/bin/env python

# -*- coding:utf-8 -*-

def application(environ, start_response):

status = '200 OK'

headers = [('Content-Type', 'text/html; charset=utf8')]

start_response(status, headers)

return [b"

Hello

"]

if __name__ == '__main__':

from wsgiref.simple_server import make_server

httpd = make_server('127.0.0.1', 8000, application)

httpd.serve_forever()

然后用你的开发工具跟进去 make_server 这个函数,看下它的定义:

# lib/python2.7/wsgiref/simple_server.py

def make_server(

host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler

):

"""Create a new WSGI server listening on `host` and `port` for `app`"""

server = server_class((host, port), handler_class)

server.set_app(app)

return server

class WSGIServer(HTTPServer):

"""BaseHTTPServer that implements the Python WSGI protocol"""

application = None

def server_bind(self):

"""Override server_bind to store the server name."""

HTTPServer.server_bind(self)

self.setup_environ()

def setup_environ(self):

# Set up base environment

env = self.base_environ = {}

env['SERVER_NAME'] = self.server_name

env['GATEWAY_INTERFACE'] = 'CGI/1.1'

env['SERVER_PORT'] = str(self.server_port)

env['REMOTE_HOST']=''

env['CONTENT_LENGTH']=''

env['SCRIPT_NAME'] = ''

def get_app(self):

return self.application

def set_app(self,application):

self.application = application

看到这个 WSGIServer 定义了吗,继承了一个 HttpServer。我们再继续追一下其定义:

# lib/python2.7/BaseHTTPServer.py

class HTTPServer(SocketServer.TCPServer):

allow_reuse_address = 1 # Seems to make sense in testing environment

def server_bind(self):

"""Override server_bind to store the server name."""

SocketServer.TCPServer.server_bind(self)

host, port = self.socket.getsockname()[:2]

self.server_name = socket.getfqdn(host)

self.server_port = port

到这里,我们继续追,看到 TcpServer 定义:

# lib/python2.7/SocketServer.py

class TCPServer(BaseServer):

"""这里我省略了定义"""

你还可以发现一个 ThreadingTCPServer 类:我们看下它的定义

class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

好了,怎么多线程处理请求呢?看下这个 ThreadingMixIn 类是如何实现的:

class ThreadingMixIn:

"""Mix-in class to handle each request in a new thread."""

# Decides how threads will act upon termination of the

# main process

daemon_threads = False

def process_request_thread(self, request, client_address):

"""Same as in BaseServer but as a thread.

In addition, exception handling is done here.

"""

try:

self.finish_request(request, client_address)

self.shutdown_request(request)

except:

self.handle_error(request, client_address)

self.shutdown_request(request)

def process_request(self, request, client_address):

"""Start a new thread to process the request."""

t = threading.Thread(target = self.process_request_thread,

args = (request, client_address))

t.daemon = self.daemon_threads

t.start()

看到吗,其实就是对于每个新请求,会启动一个新的线程来处理 socket 请求。假如让我们自己实现一个多线程 socket server 应该怎么实现呢?先来写一个简单的单线程 socker echo server:

from socket import * # 偷懒这么写

s = socket(AF_INET, SOCK_STREAM)

s.bind(("", 9000))

s.listen(5)

while True:

c, a = s.accept()

print "Received connection from", a

c.send("Hello %s\\n" % a[0])

c.close()

你可以用telnet之类的工具连上该端口看效果。 这样一次只能处理一个请求,如果想每次来一个请求用一个线程处理呢?我们可以这样做:

import threading

from socket import *

def handle_client(c):

# 处理 client 请求

c.send("Hello\n")

c.close()

return

s = socket(AF_INET, SOCK_STREAM)

s.bind(("", 9000))

s.listen(5)

while True:

c, a = s.accept()

t = threading.Thread(target=handle_client,

args=(c,))

是不是很简单,这其实就是多线程工作的原理,每次 accept 得到一个新的客户端请求以后

开一个线程去处理。当然 socket 编程还是偏底层,我们刚才看到了 python 提供了 SocketServer 模块来简化 socket 编程。我们使用 SocketServer 模块来发送数据:

import SocketServer

import time

class TimeHandler(SocketServer.BaseRequestHandler):

def handle(self):

# self.request 是一个 client socket 对象

self.request.sendall(time.ctime() + "\n")

serv = SocketServer.TCPServer(("", 8889), TimeHandler)

serv.serve_forever()

它的执行原理是这样的:server 循环等待请求到来

对每个 socket 连接,server 创建一个新的 handler 类实例

handle() 方法调用处理 client socket 对象,比如发送数据

handle() 方法返回后,连接关闭,同时 handler 实例对象销毁

但是这个 server 的处理能力很差,一次只能处理一个请求,我们看下这个模块提供了几个类:TCPServer: 同步的 tcp server

ForkingTCPServer: 多进程 tcp server

ThreadingTCPServer: 多线程 tcp server

怎么实现一个多线程 tcp server 呢?很简单:

# 改成 ThreadingTCPServer 就行了,代码其他部分不动

serv = SocketServer.ThreadingTCPServer(("",8000),TimeHandler)

serv.serve_forever()

这样一来,新的请求就能被新的线程去处理了。我们就通过线程来增强了并发能力,当然线程开销比较大,不如用协程(抽空会写个用协程实现异步的socker server)。 如果你浏览该模块,还能看到两个 Mixin:ForkingMixin

ThreadingMixIn

我们只要继承它们就很容易实现一个多进程或者多线程的 server,比如实现一个多线程的 HTTPServer

from BaseHTTPServer import HTTPServer

from SimpleHTTPServer import SimpleHTTPRequestHandler

from SocketServer import ThreadingMixIn

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):

pass

serv = ThreadedHTTPServer(("", 8080), SimpleHTTPRequestHandler)

好了,看了这么多让我们改造下 Python 自带的 wsgi server 为多线程的:

import time

import SocketServer

import socket

from SimpleHTTPServer import SimpleHTTPRequestHandler

from SocketServer import ThreadingMixIn

class HTTPServer(SocketServer.TCPServer):

allow_reuse_address = 1 # Seems to make sense in testing environment

def server_bind(self):

"""Override server_bind to store the server name."""

SocketServer.TCPServer.server_bind(self)

host, port = self.socket.getsockname()[:2]

self.server_name = socket.getfqdn(host)

self.server_port = port

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):

pass

class ThreadWSGIServer(ThreadedHTTPServer):

"""BaseHTTPServer that implements the Python WSGI protocol"""

application = None

def server_bind(self):

"""Override server_bind to store the server name."""

HTTPServer.server_bind(self)

self.setup_environ()

def setup_environ(self):

# Set up base environment

env = self.base_environ = {}

env['SERVER_NAME'] = self.server_name

env['GATEWAY_INTERFACE'] = 'CGI/1.1'

env['SERVER_PORT'] = str(self.server_port)

env['REMOTE_HOST'] = ''

env['CONTENT_LENGTH'] = ''

env['SCRIPT_NAME'] = ''

def get_app(self):

return self.application

def set_app(self, application):

self.application = application

def application(environ, start_response):

time.sleep(10) # 注意这里的 sleep

status = '200 OK'

headers = [('Content-Type', 'text/html; charset=utf8')]

start_response(status, headers)

return [b"

Hello

"]

if __name__ == '__main__':

from wsgiref.simple_server import make_server

httpd = make_server('127.0.0.1', 8000, application, server_class=ThreadWSGIServer)

httpd.serve_forever()

对了,我们怎么证明这个真是多线程的 wsgi server 了呢,很简单。你看我加了个 sleep(10)。 如果你在之前的单线程 wsgi server 跑,你可以开俩个终端去 curl,curl 完一个赶紧切到另一个 curl 。 单线程你会发现几乎是 10 + 10 秒,但是下边这个多线程 wsgi server 你会发现大概只用10秒,两个请求是并发的(当然我这种测试很 low,你可以随便用一个网络测试工具)。虽然我没看过 django wsgi server 的实现,但是它自己实现的开发服务器原理应该是类似的。

如果你能坚持看到这里,是更明白了呢还是更懵了呢?

你可以看下PegasusWang/notebooks 这个是手撸 web 框架的教程,看完你就对 wsgi,如何自己写 框架以及 gunicorn 部署有点概念了。最近在学习异步框架的工作原理,感兴趣可以看看,之后还会出个协程版本的。Pegasus Wang:Python 异步 web 框架是如何工作的[视频]​zhuanlan.zhihu.com

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值