59

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

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

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

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

4

3 回答 3

31

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

Session.prepare_request读取self.cookies, 和Session.send调用extract_cookies_to_jar(self.cookies, ...), 并且调用jar.extract_cookies(...)(jarself.cookies这种情况下)。

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

更新

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

于 2013-12-08T19:04:50.540 回答
31

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

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

于 2015-09-10T13:50:25.343 回答
1

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

  1. 它会在构造函数中初始化默认的 HTTPAdapter,并在将另一个安装到“http”或“https”时泄漏它。
  2. HTTPAdapter 实现维护了连接池,我认为它不是在每个 Session 对象实例化上创建的。
  3. Session 关闭 HTTPAdapter,因此您不能在不同的 Session 实例之间重用连接池。
  4. 根据各种讨论,会话类似乎不是线程安全的。
  5. 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。希望这会对某人有所帮助。

于 2021-09-07T21:32:02.063 回答