Skip to content

threadpool_limits is not thread-safe under ThreadPoolExecutor #205

@ZX-ModelCloud

Description

@ZX-ModelCloud

When using threadpoolctl.threadpool_limits inside multiple Python threads (e.g. via ThreadPoolExecutor), the thread limit is not reliably enforced.
Despite using with threadpool_limits(limits=2) inside each worker, some threads observe OpenBLAS using the full default thread count.

Reproducible Example

#!/usr/bin/env python3
from threadpoolctl import threadpool_limits, threadpool_info
import torch

from concurrent.futures import ThreadPoolExecutor, as_completed


def worker(i):
    # NOTE: this appears to be process-global, not thread-local
    with threadpool_limits(limits=2):
        a = torch.randn(512, 256, device="cpu")
        b = torch.randn(256, 512, device="cpu")
        _ = a @ b
        info = threadpool_info()
        print(f"[worker {i}] threadpool_info:", info)
        return info


results = []

with ThreadPoolExecutor(max_workers=8) as executor:
    futures = [executor.submit(worker, i) for i in range(8)]
    for fut in as_completed(futures):
        results.append(fut.result())

print("final threadpool_info:")
print(threadpool_info())

Actual Output

[worker 6] threadpool_info: [{'user_api': 'blas', 'internal_api': 'openblas', 'num_threads': 2, 'prefix': 'libscipy_openblas', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so', 'version': '0.3.29', 'threading_layer': 'pthreads', 'architecture': 'Haswell'}, {'user_api': 'openmp', 'internal_api': 'openmp', 'num_threads': 2, 'prefix': 'libgomp', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/torch/lib/libgomp.so.1', 'version': None}]
[worker 1] threadpool_info: [{'user_api': 'blas', 'internal_api': 'openblas', 'num_threads': 2, 'prefix': 'libscipy_openblas', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so', 'version': '0.3.29', 'threading_layer': 'pthreads', 'architecture': 'Haswell'}, {'user_api': 'openmp', 'internal_api': 'openmp', 'num_threads': 2, 'prefix': 'libgomp', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/torch/lib/libgomp.so.1', 'version': None}]
[worker 5] threadpool_info: [{'user_api': 'blas', 'internal_api': 'openblas', 'num_threads': 24, 'prefix': 'libscipy_openblas', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so', 'version': '0.3.29', 'threading_layer': 'pthreads', 'architecture': 'Haswell'}, {'user_api': 'openmp', 'internal_api': 'openmp', 'num_threads': 2, 'prefix': 'libgomp', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/torch/lib/libgomp.so.1', 'version': None}]
[worker 3] threadpool_info: [{'user_api': 'blas', 'internal_api': 'openblas', 'num_threads': 2, 'prefix': 'libscipy_openblas', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so', 'version': '0.3.29', 'threading_layer': 'pthreads', 'architecture': 'Haswell'}, {'user_api': 'openmp', 'internal_api': 'openmp', 'num_threads': 2, 'prefix': 'libgomp', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/torch/lib/libgomp.so.1', 'version': None}]
[worker 4] threadpool_info: [{'user_api': 'blas', 'internal_api': 'openblas', 'num_threads': 2, 'prefix': 'libscipy_openblas', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so', 'version': '0.3.29', 'threading_layer': 'pthreads', 'architecture': 'Haswell'}, {'user_api': 'openmp', 'internal_api': 'openmp', 'num_threads': 2, 'prefix': 'libgomp', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/torch/lib/libgomp.so.1', 'version': None}]
[worker 0] threadpool_info: [{'user_api': 'blas', 'internal_api': 'openblas', 'num_threads': 2, 'prefix': 'libscipy_openblas', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so', 'version': '0.3.29', 'threading_layer': 'pthreads', 'architecture': 'Haswell'}, {'user_api': 'openmp', 'internal_api': 'openmp', 'num_threads': 2, 'prefix': 'libgomp', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/torch/lib/libgomp.so.1', 'version': None}]
[worker 7] threadpool_info: [{'user_api': 'blas', 'internal_api': 'openblas', 'num_threads': 2, 'prefix': 'libscipy_openblas', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so', 'version': '0.3.29', 'threading_layer': 'pthreads', 'architecture': 'Haswell'}, {'user_api': 'openmp', 'internal_api': 'openmp', 'num_threads': 2, 'prefix': 'libgomp', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/torch/lib/libgomp.so.1', 'version': None}]
[worker 2] threadpool_info: [{'user_api': 'blas', 'internal_api': 'openblas', 'num_threads': 2, 'prefix': 'libscipy_openblas', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so', 'version': '0.3.29', 'threading_layer': 'pthreads', 'architecture': 'Haswell'}, {'user_api': 'openmp', 'internal_api': 'openmp', 'num_threads': 2, 'prefix': 'libgomp', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/torch/lib/libgomp.so.1', 'version': None}]
final threadpool_info:
[{'user_api': 'blas', 'internal_api': 'openblas', 'num_threads': 2, 'prefix': 'libscipy_openblas', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so', 'version': '0.3.29', 'threading_layer': 'pthreads', 'architecture': 'Haswell'}, {'user_api': 'openmp', 'internal_api': 'openmp', 'num_threads': 24, 'prefix': 'libgomp', 'filepath': '/root/miniconda3/envs/gp_py311/lib/python3.11/site-packages/torch/lib/libgomp.so.1', 'version': None}]

Some workers correctly observe num_threads = 2, while others observe num_threads = 24, even though all are wrapped in with threadpool_limits(limits=2).


Environment

Distributor ID: Ubuntu
Description: Ubuntu 22.04.5 LTS
Release: 22.04
Codename: jammy

torch: 2.9.0
numpy: 2.2.6
scipy: 1.16.2
threadpoolctl: 3.6.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions