禁止多次创建线程

时间:2013-06-30 09:01:30

标签: c++ multithreading thread-safety

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

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

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

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

3 个答案:

答案 0 :(得分:2)

不是为每个任务生成一个新线程,为什么不只是形成一些工作线程,然后使用std:packaged_taskstd::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;
}

显然,这不具备我们想要的类型安全性,如果您可以缩小返回值的类型,可以显着提高类型安全性,以防止必须返回指针{ {1}}。

答案 1 :(得分:1)

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

答案 2 :(得分:1)

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

还有一些替代解决方案,例如具有永久运行的单个线程并将数据提供给管道,消息队列或使用事件来发出信号“更多可用工作,以及类似事件”,这是“结果” 。