Python 的 Requests 库线程中的 Session 对象是不是安全?

Posted

技术标签:

【中文标题】Python 的 Requests 库线程中的 Session 对象是不是安全?【英文标题】:Is the Session object from Python's Requests library thread safe?Python 的 Requests 库线程中的 Session 对象是否安全? 【发布时间】:2013-08-13 19:50:32 【问题描述】:

Python 流行的Requests 库在其主页上据说是线程安全的,但没有给出更多细节。如果我调用requests.session(),我可以像这样安全地将这个对象传递给多个线程吗:

session = requests.session()
for i in xrange(thread_count):
    threading.Thread(
        target=target,
        args=(session,),
        kwargs=
    )

并在多个线程中使用同一个连接池发出请求?

如果是这样,这是推荐的方法,还是应该为每个线程提供自己的连接池? (假设所有单个连接池的总大小加起来就是一个大连接池的大小,就像上面的那个。)每种方法的优缺点是什么?

【问题讨论】:

你知道哪个更好吗?我目前遇到了几乎相同的问题。我正在考虑为每个线程创建一个新会话,以免成为单个连接池中所有请求的瓶颈。 @Marcel Wilson 不完全是。尽管对于我使用会话对象一遍又一遍地请求相同 URL 的项目之一,我将相同的会话对象发送到所有线程。该应用程序似乎确实有效,但我仍然不确定更好的方法是什么。但请注意,我的问题不是连接池出现瓶颈,而是打开太多连接并一次发送太多请求。 Requests 建立在 urllib3 之上。请求的线程安全很大程度上归功于 urllib3 的线程安全,其文档更详细地讨论了线程安全。 @dg123 我最终在 for 循环中创建了一个会话。每个线程都有自己的连接池。 【参考方案1】:

我也遇到了同样的问题,并去源代码寻找适合我的解决方案。 在我看来,Session 类普遍存在各种问题。

    它会在构造函数中初始化默认的 HTTPAdapter,并在您将另一个安装到“http”或“https”时泄漏它。 HTTPAdapter 实现维护连接池,我认为它不是在每个 Session 对象实例化上创建的。 Session 关闭 HTTPAdapter,因此您无法在不同 Session 实例之间重用连接池。 根据各种讨论,Session 类似乎不是线程安全的。 HTTPAdapter 在内部使用 urlib3.PoolManager。而且我在源码中没有发现任何明显的线程安全相关的问题,所以我宁愿相信文档,上面说 urlib3 是线程安全的。

作为上述列表的结论,我没有找到比重写 Session 类更好的方法

class HttpSession(Session):
    def __init__(self, adapter: HTTPAdapter):
        self.headers = default_headers()
        self.auth = None
        self.proxies = 
        self.hooks = default_hooks()
        self.params = 
        self.stream = False
        self.verify = True
        self.cert = None
        self.max_redirects = DEFAULT_REDIRECT_LIMIT
        self.trust_env = True
        self.cookies = cookiejar_from_dict()
        self.adapters = OrderedDict()
        self.mount('https://', adapter)
        self.mount('http://', adapter)

    def close(self) -> None:
        pass

并创建连接工厂,例如:

class HttpSessionFactory:
    def __init__(self,
             pool_max_size: int = DEFAULT_CONNECTION_POOL_MAX_SIZE,
             retry: Retry = DEFAULT_RETRY_POLICY):
        self.__http_adapter = HTTPAdapter(pool_maxsize=pool_max_size, max_retries=retry)

    def session(self) -> Session:
        return HttpSession(self.__http_adapter)

    def close(self):
        self.__http_adapter.close()

最后,我可以在代码的某个地方写:

with self.__session_factory.session() as session:
    response = session.get(request_url)

而且我所有的会话实例都将重用同一个连接池。 最后,当应用程序停止时,我可以关闭 HttpSessionFactory。 希望这会对某人有所帮助。

【讨论】:

【参考方案2】:

https://github.com/psf/requests/issues/1871 暗示 Session 不是线程安全的,并且至少有一位维护者建议每个线程一个 Session。

我刚刚打开https://github.com/psf/requests/issues/2766 来澄清文档。

【讨论】:

看起来这取决于urllib3 是线程安全的,我不相信它基于github.com/urllib3/urllib3/issues/1252【参考方案3】:

在查看了requests.session 的来源之后,我要说会话对象可能是线程安全的,这取决于所使用的 CookieJar 的实现。

Session.prepare_requestself.cookies 读取,Session.send 调用 extract_cookies_to_jar(self.cookies, ...),然后调用 jar.extract_cookies(...)(在这种情况下,jarself.cookies)。

Python 2.7's cookielib 的源在更新 jar 时获取了锁 (threading.RLock),因此它看起来是线程安全的。另一方面,documentation for cookielib 没有提到线程安全,所以也许不应该依赖这个特性?

更新

如果您的线程正在改变会话对象的任何属性,例如headersproxiesstream 等,或者调用mount 方法或使用带有with 语句的会话等。那么它不是线程安全的。

【讨论】:

然而,我认为如果你这样做 my_session.get(url, headers="something": "something") 这应该不是问题。

以上是关于Python 的 Requests 库线程中的 Session 对象是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章

运维python拓展requests使用

使用 Python 的 Requests 库浏览网页/点击按钮

Python学习第八篇:requests 库学习

Python学习第八篇:requests 库学习

Python学习第八篇:requests 库学习

python第三方库requests简单介绍