diff --git a/CHANGES.rst b/CHANGES.rst
index f212edd..b9ae31f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,11 +2,18 @@
  CHANGES
 =========
 
-4.0.2 (unreleased)
+4.1.0 (unreleased)
 ==================
 
-- Nothing changed yet.
+- The help namespace no longer modifies the global help object on
+  traversal. Instead it returns a new proxy object. This makes it
+  thread-safe. See `issue 4
+  <https://github.com/zopefoundation/zope.app.onlinehelp/issues/4>`_.
 
+- ``getTopicFor`` now really returns the first found topic in the
+  event that the object implements multiple interfaces that have
+  registered topics for the given view. Previously it would return the
+  topic for the least-specific interface.
 
 4.0.1 (2017-05-21)
 ==================
diff --git a/src/zope/app/onlinehelp/__init__.py b/src/zope/app/onlinehelp/__init__.py
index 9998679..5ee40bf 100644
--- a/src/zope/app/onlinehelp/__init__.py
+++ b/src/zope/app/onlinehelp/__init__.py
@@ -20,9 +20,10 @@ __docformat__ = 'restructuredtext'
 
 import os
 
-import zope
 from zope.component import getUtilitiesFor
 from zope.interface import providedBy
+from zope.proxy import ProxyBase
+from zope.proxy import non_overridable
 from zope.testing import cleanup
 
 from zope.app.onlinehelp.interfaces import IOnlineHelpTopic
@@ -34,6 +35,25 @@ path = os.path.join(os.path.dirname(__file__),
                       'help', 'welcome.stx')
 globalhelp = OnlineHelp('Online Help', path)
 
+class _TraversedOnlineHelpProxy(ProxyBase):
+    """
+    A proxy around the globalhelp object that is returned when we
+    traverse through the helpNamespace.
+
+    It adds the ``context`` attribute to the context that was traversed
+    through.
+    """
+    __slots__ = ('context',)
+
+    def __init__(self, context):
+        self.context = context
+        ProxyBase.__init__(self, globalhelp)
+
+    @non_overridable
+    def __reduce__(self, proto=None):
+        raise TypeError("Not picklable")
+    __reduce_ex__ = __reduce__
+
 
 class helpNamespace(object):
     """ help namespace handler """
@@ -42,12 +62,14 @@ class helpNamespace(object):
         self.context = context
 
     def traverse(self, name, ignored):
-        """Used to traverse to an online help topic.
-        Returns the global `OnlineHelp` instance with the traversal
-        context.
         """
-        globalhelp.context = self.context
-        return globalhelp
+        Used to traverse to an online help topic.
+
+        Returns a proxy for the global :class::`~.OnlineHelp` instance
+        with the traversal context.
+        """
+        return _TraversedOnlineHelpProxy(self.context)
+
 
 def getTopicFor(obj, view=None):
     """Determine topic for an object and optionally a view.
@@ -115,19 +137,45 @@ def getTopicFor(obj, view=None):
     >>> getTopicFor(Dummy2()) is None
     True
 
-    """
-    topic = None
-    for interface in providedBy(obj):
-        for t in getUtilitiesFor(IOnlineHelpTopic):
-            if t[1].interface==interface and t[1].view==view:
-                topic = t[1]
-                break
+    If there is a second interface also provided with the same
+    view name and registered for that interface, still only the first
+    topic will be found.
+
+    >>> from zope.interface import Interface, implementer, alsoProvides
+    >>> class I3(Interface):
+    ...     pass
+    >>> @implementer(I3)
+    ... class Dummy3(object):
+    ...     pass
+
+    >>> path = os.path.join(testdir(), 'help2.txt')
+    >>> onlinehelp.registerHelpTopic('a', 'help3', 'Help 3',
+    ...     path, I3, None)
 
-    return topic
+    >>> getTopicFor(Dummy3()).title
+    'Help 3'
+    >>> getTopicFor(Dummy1()).title
+    'Help 2'
 
+    >>> @implementer(I1, I3)
+    ... class Dummy4(object):
+    ...     pass
+    >>> getTopicFor(Dummy4()).title
+    'Help 2'
+
+    >>> @implementer(I3, I1)
+    ... class Dummy5(object):
+    ...     pass
+    >>> getTopicFor(Dummy5()).title
+    'Help 3'
+
+    """
+    for interface in providedBy(obj):
+        for _name, topic in getUtilitiesFor(IOnlineHelpTopic):
+            if topic.interface == interface and topic.view == view:
+                return topic
 
 def _clear():
-    global globalhelp
     globalhelp.__init__(globalhelp.title, globalhelp.path)
 
 
diff --git a/src/zope/app/onlinehelp/configure.zcml b/src/zope/app/onlinehelp/configure.zcml
index e68c781..3fa7e8e 100644
--- a/src/zope/app/onlinehelp/configure.zcml
+++ b/src/zope/app/onlinehelp/configure.zcml
@@ -14,6 +14,11 @@
         />
   </class>
 
+  <class class="._TraversedOnlineHelpProxy">
+	<require
+        like_class=".onlinehelp.OnlineHelp" />
+  </class>
+
   <!-- this is the generic help topic implementation -->
   <class class=".onlinehelptopic.OnlineHelpTopic">
     <require
diff --git a/src/zope/app/onlinehelp/metaconfigure.py b/src/zope/app/onlinehelp/metaconfigure.py
index 3fd867e..58918f6 100644
--- a/src/zope/app/onlinehelp/metaconfigure.py
+++ b/src/zope/app/onlinehelp/metaconfigure.py
@@ -2,21 +2,20 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
 # All Rights Reserved.
-# 
+#
 # This software is subject to the provisions of the Zope Public License,
 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
-# 
+#
 ##############################################################################
 """Meta-Configuration Handlers for "help" namespace.
 
 These handlers process the `registerTopic()` directive of
 the "help" ZCML namespace.
 
-$Id$
 """
 __docformat__ = 'restructuredtext'
 
@@ -25,7 +24,7 @@ from zope.app.onlinehelp import globalhelp
 
 class OnlineHelpTopicDirective(object):
 
-    def __init__(self, _context, id, title, parent="", doc_path=None, 
+    def __init__(self, _context, id, title, parent="", doc_path=None,
         for_=None, view=None, class_=None, resources=None):
         self._context = _context
         self.id = id
@@ -36,7 +35,7 @@ class OnlineHelpTopicDirective(object):
         self.view = view
         self.class_ = class_
         self.resources = resources
-        
+
     def _args(self):
         return (self.parent, self.id, self.title, self.doc_path, self.for_,
                 self.view, self.class_, self.resources)

The ++help namespace is not thread-safe
When you traverse the `++help` namespace, it [mutates a global object](https://github.com/zopefoundation/zope.app.onlinehelp/blob/master/src/zope/app/onlinehelp/__init__.py#L49), which is [clearly an issue if threads are involved](https://github.com/zopefoundation/zope.app.onlinehelp/pull/2#discussion_r116946254). Further, it is likely to be making that global object have a reference to a persistent object, which is Bad once the transaction ends and the ZODB Connection is closed or reused.
diff --git a/src/zope/app/onlinehelp/tests/test_onlinehelp.py b/src/zope/app/onlinehelp/tests/test_onlinehelp.py
index cfe0b29..14e6da0 100644
--- a/src/zope/app/onlinehelp/tests/test_onlinehelp.py
+++ b/src/zope/app/onlinehelp/tests/test_onlinehelp.py
@@ -70,9 +70,27 @@ class TestOnlineHelp(unittest.TestCase):
             'path that does not exist')
 
 
+class TestOnlineHelpNamespace(unittest.TestCase):
+
+    def test_context(self):
+        from zope.app.onlinehelp import globalhelp
+        from zope.app.onlinehelp import helpNamespace
+
+        traversed = helpNamespace(self).traverse(None, None)
+        self.assertIs(traversed.context, self)
+        self.assertIsNone(getattr(globalhelp, 'context', None))
+
+    def test_cannot_pickle(self):
+        from zope.app.onlinehelp import helpNamespace
+        import pickle
+
+        traversed = helpNamespace(self).traverse(None, None)
+        self.assertRaises(TypeError,
+                          pickle.dumps, traversed)
+
+
 def testdir():
-    import zope.app.onlinehelp.tests
-    return os.path.dirname(zope.app.onlinehelp.tests.__file__)
+    return os.path.dirname(__file__)
 
 def setUp(tests):
     testing.setUp()

pip install -e .[test]
parse_log_pytest
requirements.txt
zope.testrunner pytest
apt-get update apt-get install -y gcc
3.6
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
beautifulsoup4==4.12.3
BTrees==4.11.3
certifi==2021.5.30
cffi==1.15.1
docutils==0.18.1
importlib-metadata==4.8.3
iniconfig==1.1.1
multipart==1.1.0
packaging==21.3
persistent==4.9.3
pluggy==1.0.0
py==1.11.0
pycparser==2.21
pyparsing==3.1.4
pytest==7.0.1
python-gettext==4.1
pytz==2025.2
roman==3.3
six==1.17.0
soupsieve==2.3.2.post1
tomli==1.2.3
transaction==3.1.0
typing_extensions==4.1.1
waitress==2.0.0
WebOb==1.8.9
WebTest==3.0.0
zc.lockfile==2.0
ZConfig==3.6.1
zdaemon==4.4
zipp==3.6.0
ZODB==5.8.1
zodbpickle==2.6
zope.annotation==4.8
zope.app.appsetup==4.2.0
zope.app.authentication==4.0.0
zope.app.basicskin==4.0.0
zope.app.component==4.1.0
zope.app.container==4.0.0
zope.app.content==4.0.0
zope.app.file==4.0.0
zope.app.folder==4.0.0
zope.app.form==5.1.0
zope.app.http==4.0.1
zope.app.localpermission==4.1.0
-e git+https://github.com/zopefoundation/zope.app.onlinehelp.git@0cd750a10af18f630a3dd3560bbd3139a438360e#egg=zope.app.onlinehelp
zope.app.pagetemplate==4.0.0
zope.app.preference==4.1.0
zope.app.principalannotation==4.0.0
zope.app.publication==4.5
zope.app.publisher==4.3.1
zope.app.renderer==4.1.0
zope.app.rotterdam==4.0.1
zope.app.security==5.1.0
zope.app.tree==4.1.0
zope.app.wsgi==4.4
zope.authentication==4.5.0
zope.browser==2.4
zope.browsermenu==4.4
zope.browserpage==4.4.0
zope.browserresource==4.4
zope.cachedescriptors==4.4
zope.component==5.1.0
zope.componentvocabulary==2.3.0
zope.configuration==4.4.1
zope.container==4.10
zope.contenttype==4.6
zope.copy==4.3
zope.copypastemove==4.2.1
zope.datetime==4.3.0
zope.deferredimport==4.4
zope.deprecation==4.4.0
zope.dottedname==5.0
zope.dublincore==4.3.0
zope.error==5.0
zope.event==4.6
zope.exceptions==4.6
zope.filerepresentation==5.0.0
zope.formlib==5.0.1
zope.hookable==5.4
zope.i18n==4.9.0
zope.i18nmessageid==5.1.1
zope.interface==5.5.2
zope.lifecycleevent==4.4
zope.location==4.3
zope.login==2.2
zope.minmax==2.3
zope.pagetemplate==4.6.0
zope.password==4.4
zope.pluggableauth==2.3.1
zope.preference==4.1.0
zope.principalannotation==4.4
zope.principalregistry==4.3
zope.processlifetime==2.4
zope.proxy==4.6.1
zope.ptresource==4.3.0
zope.publisher==6.1.0
zope.schema==6.2.1
zope.security==5.8
zope.securitypolicy==4.3.2
zope.session==4.5
zope.site==4.6.1
zope.size==4.4
zope.structuredtext==4.4
zope.tal==4.5
zope.tales==5.2
zope.testing==5.0.1
zope.testrunner==5.6
zope.traversing==4.4.1

name: zope.app.onlinehelp
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - beautifulsoup4==4.12.3
      - btrees==4.11.3
      - cffi==1.15.1
      - docutils==0.18.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - multipart==1.1.0
      - packaging==21.3
      - persistent==4.9.3
      - pluggy==1.0.0
      - py==1.11.0
      - pycparser==2.21
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-gettext==4.1
      - pytz==2025.2
      - roman==3.3
      - six==1.17.0
      - soupsieve==2.3.2.post1
      - tomli==1.2.3
      - transaction==3.1.0
      - typing-extensions==4.1.1
      - waitress==2.0.0
      - webob==1.8.9
      - webtest==3.0.0
      - zc-lockfile==2.0
      - zconfig==3.6.1
      - zdaemon==4.4
      - zipp==3.6.0
      - zodb==5.8.1
      - zodbpickle==2.6
      - zope-annotation==4.8
      - zope-app-appsetup==4.2.0
      - zope-app-authentication==4.0.0
      - zope-app-basicskin==4.0.0
      - zope-app-component==4.1.0
      - zope-app-container==4.0.0
      - zope-app-content==4.0.0
      - zope-app-file==4.0.0
      - zope-app-folder==4.0.0
      - zope-app-form==5.1.0
      - zope-app-http==4.0.1
      - zope-app-localpermission==4.1.0
      - zope-app-pagetemplate==4.0.0
      - zope-app-preference==4.1.0
      - zope-app-principalannotation==4.0.0
      - zope-app-publication==4.5
      - zope-app-publisher==4.3.1
      - zope-app-renderer==4.1.0
      - zope-app-rotterdam==4.0.1
      - zope-app-security==5.1.0
      - zope-app-tree==4.1.0
      - zope-app-wsgi==4.4
      - zope-authentication==4.5.0
      - zope-browser==2.4
      - zope-browsermenu==4.4
      - zope-browserpage==4.4.0
      - zope-browserresource==4.4
      - zope-cachedescriptors==4.4
      - zope-component==5.1.0
      - zope-componentvocabulary==2.3.0
      - zope-configuration==4.4.1
      - zope-container==4.10
      - zope-contenttype==4.6
      - zope-copy==4.3
      - zope-copypastemove==4.2.1
      - zope-datetime==4.3.0
      - zope-deferredimport==4.4
      - zope-deprecation==4.4.0
      - zope-dottedname==5.0
      - zope-dublincore==4.3.0
      - zope-error==5.0
      - zope-event==4.6
      - zope-exceptions==4.6
      - zope-filerepresentation==5.0.0
      - zope-formlib==5.0.1
      - zope-hookable==5.4
      - zope-i18n==4.9.0
      - zope-i18nmessageid==5.1.1
      - zope-interface==5.5.2
      - zope-lifecycleevent==4.4
      - zope-location==4.3
      - zope-login==2.2
      - zope-minmax==2.3
      - zope-pagetemplate==4.6.0
      - zope-password==4.4
      - zope-pluggableauth==2.3.1
      - zope-preference==4.1.0
      - zope-principalannotation==4.4
      - zope-principalregistry==4.3
      - zope-processlifetime==2.4
      - zope-proxy==4.6.1
      - zope-ptresource==4.3.0
      - zope-publisher==6.1.0
      - zope-schema==6.2.1
      - zope-security==5.8
      - zope-securitypolicy==4.3.2
      - zope-session==4.5
      - zope-site==4.6.1
      - zope-size==4.4
      - zope-structuredtext==4.4
      - zope-tal==4.5
      - zope-tales==5.2
      - zope-testing==5.0.1
      - zope-testrunner==5.6
      - zope-traversing==4.4.1
prefix: /opt/conda/envs/zope.app.onlinehelp

src/zope/app/onlinehelp/tests/test_onlinehelp.py::TestOnlineHelpNamespace::test_cannot_pickle src/zope/app/onlinehelp/tests/test_onlinehelp.py::TestOnlineHelpNamespace::test_context
src/zope/app/onlinehelp/tests/test_onlinehelp.py::TestOnlineHelpResource::test_size src/zope/app/onlinehelp/tests/test_onlinehelp.py::TestBaseOnlineHelpTopic::test_bad_path src/zope/app/onlinehelp/tests/test_onlinehelp.py::TestOnlineHelp::test_bad_path src/zope/app/onlinehelp/tests/test_onlinehelp.py::testdir src/zope/app/onlinehelp/tests/test_onlinehelp.py::test_suite
src/zope/app/onlinehelp/configure.zcml src/zope/app/onlinehelp/__init__.py src/zope/app/onlinehelp/metaconfigure.py CHANGES.rst
src/zope/app/onlinehelp/configure.zcml src/zope/app/onlinehelp/__init__.py src/zope/app/onlinehelp/metaconfigure.py CHANGES.rst