bshoshany / thread-pool

BS::thread_pool: a fast, lightweight, and easy-to-use C++17 thread pool library
MIT License
2.21k stars 253 forks source link

[BUG] shared_ptr is not being copied in parallelize_loop #125

Closed recsater closed 1 year ago

recsater commented 1 year ago

Describe the bug

shared_ptr is not being copied in parallelize_loop.

Minimal working example

      BS::thread_pool pool;

      int x(0), y(1), z(2);
      auto k = std::make_shared<int>(4);

      pool.parallelize_loop(0, 10, [=](const int i, const int j){
        LOG(ERROR) << x << " | " << y << " | " << z << " | " << k;

        }, 2).wait();

Behavior

image

System information

(Please note that only the latest version of the thread pool library is supported.)

Additional information

used vcpkg port

image

This is fixed by removing std::forward on line 345 of the parallelize_loop function.

image

bshoshany commented 1 year ago

Hi @recsater and thanks for opening this issue! Unfortunately, removing the perfect forwarding from parallelize_loop() may introduce other issues. You can allow each loop function to access the shared pointer if you pass it by reference: simply change [=] in the lambda expression to [&] (or [=, &k] if you want to capture the other variables by copy).

recsater commented 1 year ago

Hi @recsater and thanks for opening this issue! Unfortunately, removing the perfect forwarding from parallelize_loop() may introduce other issues. You can allow each loop function to access the shared pointer if you pass it by reference: simply change [=] in the lambda expression to [&] (or [=, &k] if you want to capture the other variables by copy).

I can't use pass by reference because of the lifetime of the shared pointer. What do I do in this case?

bshoshany commented 1 year ago

I think the problem is that the lambda expression itself is a temporary object, since you define it only as a function argument. If you define the lambda expression as a proper object in the same scope as the shared pointer, then the shared pointer will be properly copied into the lambda, and then when you pass the lambda to parallelize_loop(), the use count will be incremented correctly.

Here is a quick example demonstrating that the shared pointer is indeed still owned by the lambda even after both the pointer and the lambda go out of the scope of the main thread:

#include "BS_thread_pool.hpp"

BS::synced_stream sync_out;
BS::thread_pool pool(5);

int main()
{
    BS::multi_future<void> loop_future;
    {
        std::shared_ptr<int> k = std::make_shared<int>();
        *k = 1;
        auto task = [k](int, int)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            sync_out.println(k, " -> ", *k);
        };
        loop_future = pool.parallelize_loop(0, 10, task);
    }
    loop_future.wait();
}

Output on my computer:

0000022EA316F1D0 -> 1
0000022EA316F1D0 -> 1
0000022EA316F1D0 -> 1
0000022EA316F1D0 -> 1
0000022EA316F1D0 -> 1

Hope this helps!

recsater commented 1 year ago

thank you so much, bshoshany. I really appreciate it.

bshoshany commented 1 year ago

You're welcome! :)

bshoshany commented 1 year ago

PS: If you like this project, please consider starring it!