0

在我的 Windows 消息循环中,我正在调用 std::thread 来计算一些东西(特定于游戏的东西)。我想禁止循环创建下一个线程,直到它计算出他必须计算的内容。现在我正在处理这个问题:

if( !mIsCalculating ) {
    mIsCalculating = true;
    std::thread th( Test::method, this );
    th.detach();
}

void Test::method() {
    // ...
    mIsCalculating = false;
}

但我想知道std库中是否有现有的解决方案,比如std::invokeWhenLastDone?;-)

4

3 回答 3

2

与其为每个任务生成一个新线程,不如只形成几个工作线程,然后使用std:packaged_taskandstd::future来同时计算事物,而不需要生成一个新线程的开销。

例如:

class Calculator {
public:
    Calculator() : m_bDoneFlag( false ) {
        for( auto& thread : m_arrayThreads )
        {
            thread = std::thread( [this]{
                std::unique_lock<std::mutex> lockGuard( m_mutex, std::defer_lock );

                while( !m_bDoneFlag )
                {
                    lockGuard.lock();
                    m_condTaskWaiting.wait( lockGuard, [this]{ return !m_queueTasks.empty(); } );

                    std::packaged_task<void*()> packagedTask = std::move(m_queueTasks.front());
                    m_queueTasks.pop();

                    lockGuard.unlock();

                    // Execute task:
                    packagedTask();
                }
            });
        }
    }

    ~Calculator()
    {
        m_bDoneFlag = true;

        std::unique_lock<std::mutex> lockGuard( m_mutex );
        m_queueTasks.emplace( []{ std::this_thread::sleep_for( std::chrono::milliseconds(100) ); return nullptr; } );
        m_queueTasks.emplace( []{ std::this_thread::sleep_for( std::chrono::milliseconds(100) ); return nullptr; } );
        lockGuard.unlock();
        m_condTaskWaiting.notify_all();

        for( auto& thread : m_arrayThreads )
        {
            thread.join();
        }
    }

    std::future<void*>                          AddTask( std::function<void*()> funcToAdd )
    {
        std::packaged_task<void*()> packagedTask( funcToAdd );
        std::future<void*> future = packagedTask.get_future();

        std::unique_lock<std::mutex> lockGuard( m_mutex );
        m_queueTasks.emplace( std::move(packagedTask) );
        lockGuard.unlock();
        m_condTaskWaiting.notify_one();

        return future;
    }

private:
    std::mutex                                  m_mutex;
    std::array<std::thread, 2>                  m_arrayThreads;
    std::queue<std::packaged_task<void*()>>     m_queueTasks;
    std::condition_variable                     m_condTaskWaiting;
    std::atomic<bool>                           m_bDoneFlag;
};

然后,您可以像这样使用它:

int main()
{
    Calculator myCalc;

    std::future<void*> future1 = myCalc.AddTask( []{ std::string* pszTest = new std::string("Test String"); return pszTest; } );
    std::future<void*> future2 = myCalc.AddTask( []{ std::complex<float>* pcmplxTest = new std::complex<float>( 5.0f, 10.5f ); return pcmplxTest; } );

    std::string* pszTest = reinterpret_cast<std::string*>(future1.get());
    std::complex<float>* pcmplxTest = reinterpret_cast<std::complex<float>*>(future2.get());

    std::cout << *pszTest << " and " << *pcmplxTest << std::endl;

    delete pszTest;
    delete pcmplxTest;

    return 0;
}

显然,这没有我们想要的类型安全性,如果你可以缩小返回值的类型,你就可以显着提高类型安全性,你总是需要避免返回一个指向void.

于 2013-06-30T09:48:53.027 回答
1

据我所知,这种方法不存在。但是,您可以通过让单个线程永远运行并在 Test::Method 中管理您需要的重复来解决您的问题。当需要新的计算时,主循环可以通过使用 std::condition_variable 来通知 Test::Method。

于 2013-06-30T09:12:51.667 回答
1

从技术上讲,mIsCalculating应该是一个原子值,这样您就不会在两个不同的线程中使用非原子变量时遇到问题。除此之外,假设在“UI线程”中使用它,它只会从一个线程调用,所以应该是可以接受的。

还有几种替代解决方案,例如拥有一个永远运行的单线程并将数据馈送到管道、消息队列或使用事件来表示“更多可用工作”以及与“结果如下”类似的事件。

于 2013-06-30T09:17:04.137 回答