您可以使用WSGIDaemonProcess 指令来拥有多个在单个进程中运行的工作线程,而不是拥有多个工作进程。这样,所有线程都可以共享同一个数据库连接映射。
在你的 apache 配置中有这样的东西......
# mydomain.com.conf
<VirtualHost *:80>
ServerName mydomain.com
ServerAdmin webmaster@mydomain.com
<Directory />
Require all granted
</Directory>
WSGIDaemonProcess myapp processes=1 threads=50 python-path=/path/to/django/root display-name=%{GROUP}
WSGIProcessGroup myapp
WSGIScriptAlias / /path/to/django/root/myapp/wsgi.py
</VirtualHost>
...然后您可以在您的 Django 应用程序中使用像这样简单的东西...
# views.py
import thread
from django.http import HttpResponse
# A global variable to hold the connection mappings
DB_CONNECTIONS = {}
# Fake up this "strangedb" module
class strangedb(object):
class connection(object):
def query(self, *args):
return 'Query results for %r' % args
@classmethod
def connect(cls, *args):
return cls.connection()
# View for homepage
def home(request, username='bob'):
# Remember thread ID
thread_info = 'Thread ID = %r' % thread.get_ident()
# Connect only if we're not already connected
if username in DB_CONNECTIONS:
strangedb_connection = DB_CONNECTIONS[username]
db_info = 'We reused an existing connection for %r' % username
else:
strangedb_connection = strangedb.connect(username)
DB_CONNECTIONS[username] = strangedb_connection
db_info = 'We made a connection for %r' % username
# Fake up some query
results = strangedb_connection.query('SELECT * FROM my_table')
# Fake up an HTTP response
text = '%s\n%s\n%s\n' % (thread_info, db_info, results)
return HttpResponse(text, content_type='text/plain')
...在第一次命中时产生...
Thread ID = 140597557241600
We made a connection for 'bob'
Query results for 'SELECT * FROM my_table'
...然后,在第二个...
Thread ID = 140597145999104
We reused an existing connection for 'bob'
Query results for 'SELECT * FROM my_table'
显然,当不再需要数据库连接时,您需要添加一些东西来拆除它们,但是如果没有更多关于您的应用应该如何工作的信息,就很难知道最好的方法。
更新 #1:关于 I/O 多路复用与多线程
我在生活中使用了两次线程,每次都是
恶梦。大量时间浪费在调试不可复现上
问题。我认为是事件驱动和非阻塞 I/O 架构
可能更扎实。
使用 I/O 多路复用的解决方案可能会更好,但会更复杂,并且还需要您的“strangedb”库来支持它,即它必须能够处理 EAGAIN/EWOULDBLOCK 和有能力在必要时重试系统调用。
由于 Python 的GIL,Python 中的多线程远没有其他大多数语言那么危险,从本质上讲,它使所有 Python 字节码都是线程安全的。
实际上,只有在底层 C 代码使用 Py_BEGIN_ALLOW_THREADS 宏时,线程才会并发运行,该宏与其对应的 Py_END_ALLOW_THREADS 通常围绕系统调用和 CPU 密集型操作进行包装。
这样做的好处是在 Python 代码中几乎不可能发生线程冲突,但坏处是它并不总是能在单台机器上充分利用多个 CPU 内核。
我建议上述解决方案的原因是它相对简单,并且只需要很少的代码更改,但是如果您可以详细说明您的“strangedb”库,可能会有更好的选择。拥有一个需要每个并发用户单独的网络连接的数据库似乎很奇怪。
更新 #2:关于多处理与多线程
...围绕线程的 GIL 限制似乎有点问题。
这难道不是趋势是使用分离的原因之一吗?
代替进程?
这很可能是 Python 的 multiprocessing 模块存在的主要原因,即提供跨多个 CPU 内核并发执行 Python 字节码,尽管该模块中有一个 undocumented ThreadPool 类,它使用线程而不是进程。
在您确实需要利用每个 CPU 内核上的每个 CPU 周期的情况下,“GIL 限制”肯定会出现问题,例如如果您正在编写一个必须以每秒 60 帧的速度渲染高清的电脑游戏。
但是,大多数基于 Web 的服务可能会花费大部分时间等待某事发生,例如网络 I/O 或磁盘 I/O,Python 线程将允许同时发生。
归根结底,这是性能和可维护性之间的权衡,并且鉴于硬件通常比开发人员的时间便宜得多,因此优先考虑可维护性而不是性能通常更具成本效益。
坦率地说,当您决定使用虚拟机语言(例如 Python)而不是可编译成真实机器代码的语言(例如 C)时,您就已经准备好牺牲一些性能换取方便。
另请参阅The C10K problem,了解扩展基于 Web 的服务的技术比较。