0
# Thread Limiting
1
2
Temporarily limit the number of threads used by thread pool libraries using context managers or decorators. This capability is essential for preventing oversubscription in workloads with nested parallelism.
3
4
## Capabilities
5
6
### Context Manager Interface
7
8
The primary interface for temporarily limiting thread pools.
9
10
```python { .api }
11
class threadpool_limits:
12
"""
13
Context manager and decorator for limiting thread pool sizes.
14
15
Can be used as:
16
1. Context manager: with threadpool_limits(limits=1): ...
17
2. Direct call: limiter = threadpool_limits(limits=1)
18
3. Decorator: @threadpool_limits(limits=1)
19
20
Parameters:
21
limits: int | dict | str | None
22
Thread limit specification:
23
- int: Set global limit for all selected libraries
24
- dict: Per-API or per-library limits {api/prefix: limit}
25
- 'sequential_blas_under_openmp': Special case for nested parallelism
26
- None: No limits applied (no-op)
27
28
user_api: str | None
29
API type to limit when limits is int:
30
- 'blas': Limit only BLAS libraries
31
- 'openmp': Limit only OpenMP libraries
32
- None: Limit all detected libraries
33
"""
34
35
def __init__(self, limits=None, user_api=None): ...
36
37
def __enter__(self):
38
"""Enter context manager, returns self."""
39
40
def __exit__(self, type, value, traceback):
41
"""Exit context manager, restores original limits."""
42
43
def restore_original_limits(self):
44
"""Manually restore original thread limits."""
45
46
def unregister(self):
47
"""Alias for restore_original_limits() (backward compatibility)."""
48
49
def get_original_num_threads(self):
50
"""
51
Get original thread counts before limiting.
52
53
Returns:
54
dict[str, int]: Original thread counts by user_api
55
"""
56
57
@classmethod
58
def wrap(cls, limits=None, user_api=None):
59
"""
60
Create decorator version that delays limit setting.
61
62
Returns:
63
Decorator function for use with @threadpool_limits.wrap(...)
64
"""
65
```
66
67
### Basic Usage Examples
68
69
```python
70
from threadpoolctl import threadpool_limits
71
import numpy as np
72
73
# Global thread limiting
74
with threadpool_limits(limits=1):
75
# All thread pools limited to 1 thread
76
result = np.dot(large_matrix_a, large_matrix_b)
77
78
# API-specific limiting
79
with threadpool_limits(limits=2, user_api='blas'):
80
# Only BLAS libraries limited to 2 threads
81
# OpenMP libraries keep their original limits
82
result = np.linalg.solve(A, b)
83
84
# Per-library limiting using dict
85
with threadpool_limits(limits={'libmkl_rt': 1, 'libgomp': 4}):
86
# MKL limited to 1 thread, GNU OpenMP to 4 threads
87
result = compute_intensive_operation()
88
89
# Special case for nested parallelism
90
with threadpool_limits(limits='sequential_blas_under_openmp'):
91
# Automatically handle BLAS/OpenMP interaction
92
with parallel_backend('threading', n_jobs=4): # scikit-learn example
93
result = some_parallel_computation()
94
```
95
96
### Decorator Usage
97
98
```python
99
from threadpoolctl import threadpool_limits
100
101
@threadpool_limits(limits=1)
102
def single_threaded_computation():
103
"""This function always runs with 1 thread."""
104
return np.linalg.eigvals(large_matrix)
105
106
@threadpool_limits(limits=2, user_api='blas')
107
def blas_limited_computation():
108
"""BLAS operations limited to 2 threads."""
109
return np.dot(A, B) + np.linalg.inv(C)
110
111
# Using the wrap class method
112
@threadpool_limits.wrap(limits={'openblas': 1})
113
def openblas_sequential():
114
"""OpenBLAS operations run sequentially."""
115
return np.fft.fft2(image_data)
116
```
117
118
### Advanced Limiting Patterns
119
120
```python
121
from threadpoolctl import threadpool_limits, threadpool_info
122
123
# Conditional limiting based on current state
124
current_info = threadpool_info()
125
if any(lib['num_threads'] > 8 for lib in current_info):
126
limits = 4 # Reduce high thread counts
127
else:
128
limits = None # No limiting needed
129
130
with threadpool_limits(limits=limits):
131
result = expensive_computation()
132
133
# Nested limiting (inner limits override outer)
134
with threadpool_limits(limits=4): # Outer limit
135
result1 = computation1()
136
137
with threadpool_limits(limits=1): # Inner limit overrides
138
result2 = computation2() # Runs with 1 thread
139
140
result3 = computation3() # Back to 4 threads
141
142
# Manual control with restore
143
limiter = threadpool_limits(limits=1)
144
try:
145
result = computation()
146
finally:
147
limiter.restore_original_limits()
148
```
149
150
### Error Handling
151
152
```python
153
from threadpoolctl import threadpool_limits
154
155
# Context manager handles errors gracefully
156
try:
157
with threadpool_limits(limits=1):
158
result = potentially_failing_computation()
159
except ComputationError:
160
# Thread limits are still restored even if computation fails
161
pass
162
163
# Check original limits before/after
164
limiter = threadpool_limits(limits=2)
165
original = limiter.get_original_num_threads()
166
print(f"Original BLAS threads: {original.get('blas', 'N/A')}")
167
print(f"Original OpenMP threads: {original.get('openmp', 'N/A')}")
168
```
169
170
### Special Cases
171
172
#### Sequential BLAS under OpenMP
173
174
For nested parallelism scenarios where outer parallelism uses OpenMP and inner operations use BLAS:
175
176
```python
177
from threadpoolctl import threadpool_limits
178
179
# Automatically handles BLAS/OpenMP interaction
180
with threadpool_limits(limits='sequential_blas_under_openmp'):
181
# Special logic:
182
# - If OpenBLAS with OpenMP threading: no limits applied
183
# - Otherwise: BLAS libraries limited to 1 thread
184
parallel_workload_with_blas_operations()
185
```
186
187
#### Library-Specific Limiting
188
189
```python
190
from threadpoolctl import threadpool_limits, threadpool_info
191
192
# Get specific library prefixes
193
info = threadpool_info()
194
mkl_prefix = next((lib['prefix'] for lib in info if lib['internal_api'] == 'mkl'), None)
195
196
if mkl_prefix:
197
with threadpool_limits(limits={mkl_prefix: 1}):
198
# Only MKL limited, other libraries unchanged
199
result = computation()
200
```