C ++ Будущее неверное значение в версии выпуска - PullRequest
0 голосов
/ 27 сентября 2019

Я написал небольшой класс пула потоков и адаптировал его к рекомендациям, данным pschill (https://codereview.stackexchange.com/questions/229613/c-standard-thread-threadpool).). Теперь я ставлю в очередь несколько задач, пока поток не сможет их обработать. Задача состоит из enum class State, std::mutex (для состояния) и std::packaged_task<R(Args...)>. Задача создается вызывающим экземпляром (см. Пример кода), а время жизни гарантируется и проверяется. Как только поток готов, задача обрабатывается.здесь все прекрасно работает в каждой конфигурации сборки.

Выполнение сборки Debug x64 (последняя версия VCpp) дало мне ожидаемое значение 4. Использование любой другой конфигурации приводит к «случайному» значению....

Я проверил время жизни каждого задействованного объекта, как и ожидалось.

Выполнение задачи

{
    // May unnecessary but added due to previous errors
    std::lock_guard<std::mutex> 
    m_state = State::INPROGESS;
}

m_task(args...);

{
    std::lock_guard<std::mutex> lock(m_statelock);
    m_state = State::DONE;
}

Задача очереди

template <typename R, typename ... Args>
void addTask(Task<R, Args...>& task, Args ... args) {
    // m_tasks is a std::deque<std::function<void()>>
    std::lock_guard<std::mutex> lock(m_tasklock);
    m_tasks.emplace_back([&]() -> void {
        task.execute(args...);
    });

    // Has own mutex
    m_task_avaiable.notify_one(); // std::condition_variable
}

Процедура объекта потока

[&]() -> void {
    while (true) {
        while (tpool.m_tasks.size() > 0) {
            {
                // Added due to crash with Release-config at asgining
                // atomic<State> in Task
                std::lock_guard<std::mutex> lock(statelock);
                state = State::INPROGRESS;
            }

            std::function<void()> job;

            {
                std::lock_guard<std::mutex> lock(tpool.m_tasklock);
                if (tpool.m_tasks.size() > 0) {
                    job = std::move(tpool.m_tasks[0]);
                    tpool.m_tasks.pop_front();
                }
            }

            if (job) { job(); }
    }

        {
            std::lock_guard<std::mutex> lock(statelock);
            state = State::IDLE;
        }
        tpool.m_task_avaiable.wait(lock);
        {
            std::lock_guard<std::mutex> lock(statelock);
            if (state == State::STOP) {
                return; // To stop thread and make it joinable
            }

        }
    }
}

Main

frm::util::ThreadPool pool(2);

frm::util::Task<int, int, int> t1([](int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "Yey!...\n";
    return a + b;
});


pool.addTask(t1, 2, 2);

// Direct access to member std::packaged_task<...> just for debugging
std::cout << t1.m_task.get_future().get() << '\n';

Если онопомогает, дамп объекта задачи (x32), показывающий, что было запрошено будущее (байт 61):

 class std::mutex   48 Byte
    1   0x02 0x00 0x00 0x00
    5   0xbc 0x78 0x9f 0x0f
    9   0x00 0x00 0x00 0x00
   13   0x00 0x00 0x00 0x00
   17   0x00 0x00 0x00 0x00
   21   0x00 0x00 0x00 0x00
   25   0x00 0x00 0x00 0x00
   29   0x00 0x00 0x00 0x00
   33   0x00 0x00 0x00 0x00
   37   0x00 0x00 0x00 0x00
   41   0xff 0xff 0xff 0xff
   45   0x00 0x00 0x00 0x00
 struct std::atomic<enum frm::util::Task<int,int,int>::State>    4 Byte
   49   0x02 0x00 0x00 0x00
 class std::packaged_task<int __cdecl(int,int)>   12 Byte
   53   0x50 0xbd 0x15 0x01
   57   0x00 0x00 0x00 0x00
   61   0x01 0x00 0x00 0x00

Здесь есть какие-либо проблемы с std::future<T>, std::shared_future<T> или std::packaged_task<T> в VCpp19 или я что-то пропустил?

Я могу показать больше кода, но из-за правил и рекомендаций SO я скопировалкак можно меньше.

Может также помочь:

Перед использованием addTask(Task<R, Args...>& task, Args ... args) addTask(Task<R, Args...>* task, Args ... args) вызвала ошибку (не в отладке x64), если была добавлена ​​задержка, например:

// Direct access to member std::packaged_task<...> just for debugging
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << t1.m_task.get_future().get() << '\n';

1 Ответ

3 голосов
/ 27 сентября 2019

У вас неопределенное поведение в приведенном ниже коде:

 m_tasks.emplace_back([&]() -> void {
      task.execute(args...);
   });

ваша лямбда захватывает args с помощью REFERENCE.emplace_back переводит функтор в m_tasks, затем заключающая область завершается, и когда вызывается задача, вы получаете доступ к свисающей ссылке.

Вы можете передать ее по значению:

 m_tasks.emplace_back([=]() -> void {
    task.execute(args...);
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...