Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
limit APIs will automatically call that API when they are done.
https://github.com/joblib/threadpoolctl/pull/275
https://github.com/joblib/threadpoolctl/pull/289
https://github.com/joblib/threadpoolctl/pull/294

- Add a new API to get a cached `ThreadpoolController` instance
(`get_cached_controller()`), as an optimization for cases where limits are set
Expand Down Expand Up @@ -50,6 +51,15 @@
- Dropped official support for Python 3.9.
https://github.com/joblib/threadpoolctl/pull/255

- Fixed recursive `@threadpool_limits.wrap()` calls so they restore limits
correctly.
https://github.com/joblib/threadpoolctl/pull/294

- Fixed `@threadpool_limits.wrap()` so it can use third-party libraries that
were registered or imported after the function was decorated.
https://github.com/joblib/threadpoolctl/pull/294


3.7.0 (2026-09-15)
==================

Expand Down
54 changes: 40 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ True

## Usage when not using Python threads: Restricting Controlled Library Thread Pool Sizes

There are two scenarios in which you might want to use `threadpoolctl`; each
There are two scenarios in which you might want to use `threadpoolctl`; each
requires you to use different APIs.

1. You do not expect to use any Python threads, so all the work will be started
Expand Down Expand Up @@ -276,11 +276,7 @@ OpenBLAS+pthreads) and in each worker thread.
> Windows threading layer, setting the limit affects the whole process, because
> it uses a process-wide shared thread pool. (This in contrast to some other
> BLAS/OpenMP libraries, or OpenBLAS backed by OpenMP, that allow setting the
> limit per-thread.) Having multiple Python threads set limits in this case
> makes no sense, since they would effectively be arguing over a globally shared
> resource. ``threadpoolctl`` therefore constrains the limiting API in the
> OpenBLAS pthreads/Windows case to only work from the first thread that uses
> it, the so-called controlling thread.
> limit per-thread.)
>
> For many other libraries, for example OpenMP, the limit is set per thread. As
> a result, you want to set limits both ways in generic code that doesn't know
Expand Down Expand Up @@ -378,10 +374,37 @@ with CONTROLLER.limit(limits=2):

## Usage: Additional APIs and details

### OpenBLAS with pthreads/Windows
### OpenBLAS with pthreads/Windows, and the controlling thread

As mentioned above, OpenBLAS with pthreads or Windows threading layer uses a
process-wide shared thread pool. As a result, there are only two limits that
process-wide shared thread pool. Having multiple Python threads set limits in this case
makes no sense, since they would effectively be arguing over a globally shared
resource.

``threadpoolctl`` therefore constrains the limiting API in the OpenBLAS
pthreads/Windows case to only work from the first thread that uses it, the
so-called controlling thread. The controlling thread can change the limits as
normal. For other thread, calls will be silently ignored.

When using `with threadpool_limits()` as a context manager or
`@threadpool_limits.wrap()` as a decorator, the controlling thread should be
automatically cleared when the code leaves the relevant block. This allows
another thread to take control. You can also manually release the controlling
thread by calling `OpenBLASController.clear_controlling_thread()` from the
controlling thread; passing `force=True` allows clearing it from any thread.

Note that if you call a standalone `threadpool_limits()` or similar API, and
then later use the `with threadpool_limits()` or similar context
manager/decorator API, clearing the controlling thread will still happen, as
standalone limiting calls are not tracked.

NumPy and SciPy, when installed from PyPI for example, can each have their own
separate OpenBLAS with its own thread pool. The controlling thread will apply to
both.

#### Choosing limits for a process-wide shared thread pool

Given a shared OpenBLAS process pool, logically there are only two limits that
make sense in most circumstances:

* A limit of `1`, which effectively means no OpenBLAS thread pool: all
Expand All @@ -392,7 +415,9 @@ make sense in most circumstances:
you are calling BLAS from the main thread and there are no Python threads.

While `threadpoolctl` does not limit you to these two values, you may wish to do
so yourself in your code.
so yourself in your code, after benchmarking to ensure that this actually
maximizes performance. Benchmarking is important because empirically, these
limits do not always maximize performance.

### Switching the FlexiBLAS backend

Expand Down Expand Up @@ -548,7 +573,7 @@ with controller.limit(limits="sequential_blas_under_openmp"):
on Windows, the setting is process-wide and impacts the size of a process-wide
thread pool shared across all threads in the process.

## <div id="semantics"> Semantics of thread limiting </div>
## <div id="semantics"> Semantics of thread limiting </div>

Setting the number of threads may seem like a simple operation, but in practice
it can do quite different things depending on the underlying third-party library
Expand Down Expand Up @@ -593,7 +618,7 @@ When there is a choice between different APIs, `threadpoolctl` will prefer APIs
that:

1. Have a per-thread worker pool (so each Python thread gets its own pool from
the underlying third-party library)
the underlying third-party library).
2. Only affect the current thread.

Current libraries where `threadpoolctl` explicitly makes this choice are:
Expand All @@ -605,9 +630,10 @@ Current libraries where `threadpoolctl` explicitly makes this choice are:
sequential BLAS calls within an OpenMP parallel region, see
[Sequential BLAS within OpenMP parallel region](#sequential-blas-within-openmp-parallel-region).

When using OpenMP, this is also the default on Linux and macOS. On Windows
OpenMP has a per-thread worker pool but the limiting API affects all threads in
the process, not just the current one.
When using OpenMP, a limit specific to the current is also the default on Linux
and macOS. On Windows OpenMP has a per-thread worker pool but the limiting API
When using OpenMP, a limit specific to the current thread is also the default on
Linux and macOS. On Windows OpenMP has a per-thread worker pool but the limiting API

### Checking the behavior of your installed libraries

Expand Down
Loading
Loading