diff --git a/ddtrace/sampler.py b/ddtrace/sampler.py
index 7d14005ee..11a43db9e 100644
--- a/ddtrace/sampler.py
+++ b/ddtrace/sampler.py
@@ -2,8 +2,6 @@
 
 Any `sampled = False` trace won't be written, and can be ignored by the instrumentation.
 """
-from threading import Lock
-
 from .compat import iteritems
 from .internal.logger import get_logger
 
@@ -50,15 +48,6 @@ class RateSampler(object):
         return sampled
 
 
-def _key(service=None, env=None):
-    service = service or ''
-    env = env or ''
-    return 'service:' + service + ',env:' + env
-
-
-_default_key = _key()
-
-
 class RateByServiceSampler(object):
     """Sampler based on a rate, by service
 
@@ -66,34 +55,40 @@ class RateByServiceSampler(object):
     The sample rate is kept independently for each service/env tuple.
     """
 
+    @staticmethod
+    def _key(service=None, env=None):
+        """Compute a key with the same format used by the Datadog agent API."""
+        service = service or ''
+        env = env or ''
+        return 'service:' + service + ',env:' + env
+
     def __init__(self, sample_rate=1):
-        self._lock = Lock()
-        self._by_service_samplers = {}
-        self._by_service_samplers[_default_key] = RateSampler(sample_rate)
+        self.sample_rate = sample_rate
+        self._by_service_samplers = self._get_new_by_service_sampler()
 
-    def _set_sample_rate_by_key(self, sample_rate, key):
-        with self._lock:
-            if key in self._by_service_samplers:
-                self._by_service_samplers[key].set_sample_rate(sample_rate)
-            else:
-                self._by_service_samplers[key] = RateSampler(sample_rate)
+    def _get_new_by_service_sampler(self):
+        return {
+            self._default_key: RateSampler(self.sample_rate)
+        }
 
     def set_sample_rate(self, sample_rate, service='', env=''):
-        self._set_sample_rate_by_key(sample_rate, _key(service, env))
+        self._by_service_samplers[self._key(service, env)] = RateSampler(sample_rate)
 
     def sample(self, span):
         tags = span.tracer().tags
         env = tags['env'] if 'env' in tags else None
-        key = _key(span.service, env)
-        with self._lock:
-            if key in self._by_service_samplers:
-                return self._by_service_samplers[key].sample(span)
-            return self._by_service_samplers[_default_key].sample(span)
+        key = self._key(span.service, env)
+        return self._by_service_samplers.get(
+            key, self._by_service_samplers[self._default_key]
+        ).sample(span)
 
     def set_sample_rate_by_service(self, rate_by_service):
+        new_by_service_samplers = self._get_new_by_service_sampler()
         for key, sample_rate in iteritems(rate_by_service):
-            self._set_sample_rate_by_key(sample_rate, key)
-        with self._lock:
-            for key in list(self._by_service_samplers):
-                if key not in rate_by_service and key != _default_key:
-                    del self._by_service_samplers[key]
+            new_by_service_samplers[key] = RateSampler(sample_rate)
+
+        self._by_service_samplers = new_by_service_samplers
+
+
+# Default key for service with no specific rate
+RateByServiceSampler._default_key = RateByServiceSampler._key()

Stuck in "Waiting for GIL"
Hi,

We found a thread that seems blocked forever:

```
gdb python 30107
```

Here's the single thread:

```
(gdb) info threads
  Id   Target Id         Frame
* 1    Thread 0x7f1dd852e4c0 (LWP 30107) "/opt/simon/rele" 0x00007f1dd7d2e146 in do_futex_wait.constprop () from /lib64/libpthread.so.0
```

Here's the backtrace:

```
raceback (most recent call first):
  Waiting for the GIL
  File "/opt/simon/releases/b038662ce7d140609b1e4883a3ea0edf77851537/venv/lib/python2.7/site-packages/ddtrace/sampler.py", line 88, in sample
    with self._lock:
  File "/opt/simon/releases/b038662ce7d140609b1e4883a3ea0edf77851537/venv/lib/python2.7/site-packages/ddtrace/tracer.py", line 251, in start_span
    if self.priority_sampler.sample(span):
  File "/opt/simon/releases/b038662ce7d140609b1e4883a3ea0edf77851537/venv/lib/python2.7/site-packages/ddtrace/tracer.py", line 376, in trace
    span_type=span_type,
  File "/opt/simon/releases/b038662ce7d140609b1e4883a3ea0edf77851537/venv/lib/python2.7/site-packages/ddtrace/contrib/django/cache.py", line 56, in wrapped
    with tracer.trace('django.cache', span_type=TYPE, service=cache_service_name) as span:
```

I'm not sure how to troubleshoot from here.  Have you seen this before, or have any suggestions on what I could look at?  It's the only thread in process so nothing should be holding the GIL.
diff --git a/tests/test_sampler.py b/tests/test_sampler.py
index 0fa71fd59..7598ba268 100644
--- a/tests/test_sampler.py
+++ b/tests/test_sampler.py
@@ -3,7 +3,7 @@ from __future__ import division
 import unittest
 
 from ddtrace.span import Span
-from ddtrace.sampler import RateSampler, AllSampler, _key, _default_key
+from ddtrace.sampler import RateSampler, AllSampler, RateByServiceSampler
 from ddtrace.compat import iteritems
 from tests.test_tracer import get_dummy_tracer
 from ddtrace.constants import SAMPLING_PRIORITY_KEY, SAMPLE_RATE_METRIC_KEY
@@ -56,14 +56,16 @@ class RateSamplerTest(unittest.TestCase):
 
 class RateByServiceSamplerTest(unittest.TestCase):
     def test_default_key(self):
-        assert 'service:,env:' == _default_key, 'default key should correspond to no service and no env'
+        assert (
+            'service:,env:' == RateByServiceSampler._default_key
+        ), 'default key should correspond to no service and no env'
 
     def test_key(self):
-        assert _default_key == _key()
-        assert 'service:mcnulty,env:' == _key(service='mcnulty')
-        assert 'service:,env:test' == _key(env='test')
-        assert 'service:mcnulty,env:test' == _key(service='mcnulty', env='test')
-        assert 'service:mcnulty,env:test' == _key('mcnulty', 'test')
+        assert RateByServiceSampler._default_key == RateByServiceSampler._key()
+        assert 'service:mcnulty,env:' == RateByServiceSampler._key(service='mcnulty')
+        assert 'service:,env:test' == RateByServiceSampler._key(env='test')
+        assert 'service:mcnulty,env:test' == RateByServiceSampler._key(service='mcnulty', env='test')
+        assert 'service:mcnulty,env:test' == RateByServiceSampler._key('mcnulty', 'test')
 
     def test_sample_rate_deviation(self):
         for sample_rate in [0.1, 0.25, 0.5, 1]:

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-benchmark mock
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
certifi==2021.5.30
-e git+https://github.com/DataDog/dd-trace-py.git@411f218f5ce906c2328ea0e4321d334a5d8e6705#egg=ddtrace
importlib-metadata==4.8.3
iniconfig==1.1.1
mock==5.2.0
packaging==21.3
pluggy==1.0.0
psutil==7.0.0
py==1.11.0
py-cpuinfo==9.0.0
pyparsing==3.1.4
pytest==7.0.1
pytest-benchmark==3.4.1
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: dd-trace-py
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
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - mock==5.2.0
      - packaging==21.3
      - pluggy==1.0.0
      - psutil==7.0.0
      - py==1.11.0
      - py-cpuinfo==9.0.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-benchmark==3.4.1
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/dd-trace-py

tests/test_sampler.py::RateByServiceSamplerTest::test_default_key tests/test_sampler.py::RateByServiceSamplerTest::test_key
tests/test_sampler.py::RateSamplerTest::test_deterministic_behavior tests/test_sampler.py::RateSamplerTest::test_sample_rate_deviation tests/test_sampler.py::RateByServiceSamplerTest::test_sample_rate_deviation tests/test_sampler.py::RateByServiceSamplerTest::test_set_sample_rate_by_service
ddtrace/sampler.py
ddtrace/sampler.py