Skip to content
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

On utilizing current thread with ThreadPool #930

Open
l4l opened this issue Apr 21, 2022 · 4 comments
Open

On utilizing current thread with ThreadPool #930

l4l opened this issue Apr 21, 2022 · 4 comments

Comments

@l4l
Copy link

l4l commented Apr 21, 2022

I'd like to utilize current thread while using a ThreadPool, how shall I do that? Consider this example:

let do_some_work = ...;

let output = ThreadPoolBuilder::new()
    // ...
    .build()
    .unwrap()
    .install(|| do_some_work());

This code will block main thread until do_some_work is completed. But the problem is that the execution is performed in newly created pool without utilizing current running thread (it just waits for result).

One may think about overriding spawn_handler with using the current thread instead of first thread spawn:

let do_some_work = ...;

let (tx, rx) = mpsc::sync_channel(1);
let tx = Mutex::new(Some(tx));

let (tx_res, rx_res) = mpsc::sync_channel(1);

ThreadPoolBuilder::new()
    .spawn_handler(|t| {
        // Steal the work from the pool to run it on main thread
        if let Some(tx) = tx.lock().unwrap().take() {
            tx.send(t);
            return Ok(());
        }
    
        thread::spawn(move || t.run());
        Ok(())
    })
    // ...
    .build()
    .unwrap()
    .spawn(move || {
        let output = do_some_work();
        tx_res.send(output);
    });
    
let t = rx.try_recv().unwrap();
t.run();

let output = rx_res.recv().unwrap();

Which is great and works for do_some_work: 'static && do_some_work::Output: 'static. But these are actually redundant requirements for this particular case (i.e do_some_work overlives ThreadPool).

Probably related:

@cuviper
Copy link
Member

cuviper commented Apr 25, 2022

Huh, that's pretty clever! I will caution one caveat that the current thread must not be a part of a pool already, or else t.run() will fail an assertion when it tries to set its thread-local WorkerThread pointer.

But the problem is that the execution is performed in newly created pool without utilizing current running thread (it just waits for result).

I generally wouldn't worry about that though -- it should sleep with no overhead, apart from the memory use of the thread stack. Is there something that makes this more of a concern for you?

Which is great and works for do_some_work: 'static && do_some_work::Output: 'static. But these are actually redundant requirements for this particular case (i.e do_some_work overlives ThreadPool).

That's a requirement of spawn, and the usual fix is to use scope or in_place_scope, but I think that won't help your case. Scopes are blocking to enforce their lifetime, and while you could do your sneaky t.run() within, it would never be allowed to return because the scope would be holding the pool open too.

@l4l
Copy link
Author

l4l commented Apr 26, 2022

apart from the memory use of the thread stack. Is there something that makes this more of a concern for you?

Yeah, exactly. The real example actually utilizes an external pool (i.e just sends ThreadBuilder over channel at spawn_handler) and ThreadPoolBuilder is created quite frequently. Besides, I set a fixed core per each thread (sched_setaffinity) so main thread in that case just wastes entire core.

Wouldn't it be possible to link newly created ThreadPool lifetime to some kind spawn_scoped function?

@cuviper
Copy link
Member

cuviper commented Apr 26, 2022

How would you do that, when there is no lifetime parameter on ThreadPool?

Note that the problem is not really about keeping the pool alive, but rather how to ensure that it executes the spawn while its borrows are still alive. When the spawn is 'static we don't have to worry about it, just get around to it eventually. But if the spawn is tied to some local lifetime 'a, we have to block on that lifetime somehow.

The scope API does this with a callback, blocking before it returns. Once upon a time, the standard library tried to have thread::scoped with a JoinGuard, but that was found to be unsound, described here and here.

@l4l
Copy link
Author

l4l commented Apr 27, 2022

Yeah, I was thinking about API similar to JoinGuard, that's an interesting case, thanks for links. Tried to think about other ways but it seems either vulnerable to the JoinGuard issue or need to block similarly to install.

Moreover I'm not sure it's even possible to get it around for a generic case (i.e fn in_pool<R: Send>(f: impl FnOnce() -> R + Send) -> R { /* build & run thread pool there */ }) with unsafe user code if there's some kind of ThreadPool::finish. Because there's no way to transmute R: 'a -> R: 'static.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants