Skip to content

Commit c5aa3ea

Browse files
Merge pull request #54 from python-thread/perf/thread-killing
Improving thread performance
2 parents 9dc7e1c + 49e3558 commit c5aa3ea

File tree

1 file changed

+39
-40
lines changed

1 file changed

+39
-40
lines changed

src/thread/thread.py

+39-40
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class ParallelProcessing: ...
1111

1212
import sys
1313
import time
14+
import ctypes
1415
import signal
1516
import threading
1617
from functools import wraps
@@ -32,7 +33,7 @@ class ParallelProcessing: ...
3233
HookFunction,
3334
)
3435
from typing_extensions import Generic, ParamSpec
35-
from typing import List, Callable, Optional, Union, Mapping, Sequence, Tuple, Generator
36+
from typing import List, Optional, Union, Mapping, Sequence, Tuple, Generator
3637

3738

3839
Threads: set['Thread'] = set()
@@ -56,7 +57,6 @@ class Thread(threading.Thread, Generic[_Target_P, _Target_T]):
5657

5758
# threading.Thread stuff
5859
_initialized: bool
59-
_run: Callable
6060

6161
def __init__(
6262
self,
@@ -116,23 +116,29 @@ def _wrap_target(
116116
def wrapper(
117117
*args: _Target_P.args, **kwargs: _Target_P.kwargs
118118
) -> Union[_Target_T, None]:
119-
self.status = 'Running'
119+
try:
120+
self.status = 'Running'
120121

121-
global Threads
122-
Threads.add(self)
122+
global Threads
123+
Threads.add(self)
123124

124-
try:
125-
self._returned_value = target(*args, **kwargs)
126-
except Exception as e:
127-
if not any(isinstance(e, ignore) for ignore in self.ignore_errors):
128-
self.status = 'Errored'
129-
self.errors.append(e)
130-
return
125+
try:
126+
self._returned_value = target(*args, **kwargs)
127+
except Exception as e:
128+
if not any(isinstance(e, ignore) for ignore in self.ignore_errors):
129+
self.status = 'Errored'
130+
self.errors.append(e)
131+
return
132+
133+
self.status = 'Invoking hooks'
134+
self._invoke_hooks()
135+
Threads.remove(self)
136+
self.status = 'Completed'
131137

132-
self.status = 'Invoking hooks'
133-
self._invoke_hooks()
134-
Threads.remove(self)
135-
self.status = 'Completed'
138+
except SystemExit:
139+
self.status = 'Killed'
140+
print('KILLED ident: %s' % self.ident)
141+
return
136142

137143
return wrapper
138144

@@ -157,27 +163,6 @@ def _handle_exceptions(self) -> None:
157163
for e in self.errors:
158164
raise e
159165

160-
def global_trace(self, frame, event: str, arg) -> Optional[Callable]:
161-
if event == 'call':
162-
return self.local_trace
163-
164-
def local_trace(self, frame, event: str, arg):
165-
if self.status == 'Kill Scheduled' and event == 'line':
166-
print('KILLED ident: %s' % self.ident)
167-
self.status = 'Killed'
168-
raise SystemExit()
169-
return self.local_trace
170-
171-
def _run_with_trace(self) -> None:
172-
"""This will replace `threading.Thread`'s `run()` method"""
173-
if not self._run:
174-
raise exceptions.ThreadNotInitializedError(
175-
'Running `_run_with_trace` may cause unintended behaviour, run `start` instead'
176-
)
177-
178-
sys.settrace(self.global_trace)
179-
self._run()
180-
181166
@property
182167
def result(self) -> _Target_T:
183168
"""
@@ -274,17 +259,33 @@ def kill(self, yielding: bool = False, timeout: float = 5) -> bool:
274259
275260
Returns
276261
-------
277-
:returns bool: False if the it exceeded the timeout
262+
:returns bool: False if the it exceeded the timeout without being killed
278263
279264
Raises
280265
------
266+
ValueError: If the thread ident does not exist
281267
ThreadNotInitializedError: If the thread is not initialized
282268
ThreadNotRunningError: If the thread is not running
283269
"""
284270
if not self.is_alive():
285271
raise exceptions.ThreadNotRunningError()
286272

287273
self.status = 'Kill Scheduled'
274+
275+
res: int = ctypes.pythonapi.PyThreadState_SetAsyncExc(
276+
ctypes.c_long(self.ident), ctypes.py_object(SystemExit)
277+
)
278+
279+
if res == 0:
280+
raise ValueError('Thread IDENT does not exist')
281+
elif res > 1:
282+
# Unexpected behaviour, something seriously went wrong
283+
# https://docs.python.org/3/c-api/init.html#c.PyThreadState_SetAsyncExc
284+
ctypes.pythonapi.PyThreadState_SetAsyncExc(self.ident, None)
285+
raise SystemError(
286+
f'Killing thread with ident [{self.ident}] failed!\nPyThreadState_SetAsyncExc returned: {res}'
287+
)
288+
288289
if not yielding:
289290
return True
290291

@@ -308,8 +309,6 @@ def start(self) -> None:
308309
if self.is_alive():
309310
raise exceptions.ThreadStillRunningError()
310311

311-
self._run = self.run
312-
self.run = self._run_with_trace
313312
super().start()
314313

315314

0 commit comments

Comments
 (0)