New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
concurrent.future.ThreadPoolExecutor should parameterize class used for threads #89502
Comments
|
Currently the only way to use a class other than For example, suppose I have a class that applies some custom logic when running code in a new thread: Using this class with That's a bummer, because now I have to maintain this method if there are upstream fixes/changes. I also can't count on it existing in the future, since it's a private method. In other words, Here's what I'd like to be able to do instead: |
|
Can you apply some custom logic by specifying the initializer? |
Not sure that I follow; how would I use a context manager with the initializer? Of course this is just one example. In the
But one cannot use a subclass of |
|
You can call __enter__ in the initializer. Seems there is no easy way to call __exit__ at shutting down the thread, perhaps something like per-thread atexit could help. As an example, you can save the context manager in thread locals and manually repeatedly submit a function that calls __exit__ and blocks the thread on some Barrier before calling ThreadPoolExecutor.shutdown(). It is complicated, so we may add some helpers to support context managers. What are examples of your some_important_context()? Is it important to call some code before destroying the thread? The problem with allowing the user to specify the Thread subclass is that in general using Thread is an implementation detail. ThreadPoolExecutor could be implemented using the low-level _thread module instead. Or in future it can need to create a special Thread subclass, and user-specified Thread subclass can be not compatible with it. It is safer to limit ways in which the user can affect execution. The initializer parameter was added to address cases similar to your. Note also that ThreadPoolExecutor and ProcessPoolExecutor have almost identical interface. If we add some feature in ThreadPoolExecutor we will have a pressure to add the same feature in ProcessPoolExecutor to solve similar problems. |
To be honest, pulled a context manager example out of thin air to illustrate the point. But sure, I want to allocate a resource before the thread runs, and release it when the work is done.
This is surprising to hear, since I imagine that there are many potential users of this library that are evolving from direct wrangling of Thread objects, where custom Thread subclasses are commonplace. This was certainly the scenario that prompted me to post. Ultimately it's up to the maintainers what direction the library will go. Are there specific plans to adopt an alternative implementation that is orthogonal to |
|
It also occurs to me that even if |
|
I was looking for some way to be able to add a thread finalizer, a piece of code to be called when the thread pool shuts down and all threads need cleaning up. Glad I came across this issue, since the example of using a Thread subclass with a custom run (wrapped in a context manager) would fill my needs completely.
I currently have a use case with a web-framework that has persistent DB connections - i.e. they span multiple HTTP requests. This also applies in the context of running a command-line script with said framework where database connections are opened. We are calling external APIs (IO heavy), so using a ThreadPoolExecutor makes sense. However, since those "DB connection pools" are thread locals, we need to ensure that database connections are closed again to not leak resources. The current workaround is to submit a job/function to the pool and have it close the database connections, which adds overhead since database connections are now opened and closed within the same thread that could have been re-used. Using a context manager, we would be able to wrap the Furthermore I like the idea of being able to provide the class as a context manager kwarg, but would also not be opposed to a class property specifying the Thread class to use - both would greatly enhance composability and are a cleaner solution than adding a finalizer option (similar to the |
|
I guess if you are asking for initialization and finalization of thread-specific data in a thread pool -- you need exactly these things (or a context manager). |
|
As in most cases, there are many different ways to achieve the same result in Python. I'm not sure that I follow the assertion that using a custom |
|
I think overriding the "target" function would be better. Providing some "thread" or "process" class is too fragile. If the executors require custom bits on those objects you need to be careful to implement and not squash them. Overriding the "target" function would also satisfy OP requirement without the need for a class / additional object. And if someone needs an object they can provide one that implements The API would be something like this.
def worker_fn(worker, initargs):
"""
Wraps the worker function provided by the executor.
The worker argument is an opaque callable provided by the executor. It will perform the
actual work of the Thread / Process.
Arguments:
worker (callable): opaque ; must be called
initargs (mixed): whatever was provided as initargs to the executor
"""
# The following is just an example user implementation of "worker_fn"
with some_context():
db = connect.open()
worker()
db.close()As for the library side. A naive implementation would be like (a bunch of stuff is left out). class ProcessPoolExecutor():
def _spawn_process(self):
worker = partial(
_process_worker,
self._result_queue,
self._initializer,
self._initargs,
self._max_tasks_per_child
)
if self._worker_fn:
p = self._mp_context.Process(
target=self._worker_fn,
args=(worker, self._initargs),
)
else:
p = self._mp_context.Process(target=worker)
p.start()
self._processes[p.pid] = pBaring this, I'd be happy with a finalizer. My use case is SharedMemory. According to the docs you're supposed to ".close()" the SharedMemory when done. That's a little hard to do when you can't guarantee some final function. You either override something in the standard library or |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: