2013-06-30 2 views
0

В моем цикле сообщений Windows я вызываю std :: thread для вычисления чего-то (определенного для игры материала). Я хотел запретить цикл для создания следующего потока, пока он не вычислил, что ему нужно было вычислить. Сейчас я имею дело с этим таким образом:Запретить создание потока несколько раз

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

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

, но мне интересно, есть ли существующее решение в библиотеке станд, как станд :: invokeWhenLastDone? ;-)

+0

Без блокировки, если этот код будет вызываться дважды в одно и то же время, вы можете создать 2 потока. Если вы вызываете это только из одного конкретного потока, это не проблема. – asafrob

+0

std :: invokeWhenLastDoneWhatExactly? –

ответ

2

Вместо порождение нового потока для каждой задачи, почему бы не просто сформировать несколько рабочих потоков, а затем использовать std:packaged_task и std::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.

+0

'AddTask' и' ~ Calculator' имеют доступ к 'm_queueTasks' без' m_mutex', что приводит к гонке данных. – Casey

+0

@Casey Спасибо за указание этого! –

+0

Вероятно, еще один случай, когда OP читает какой-то модный «сайт/страницу Intro to Threads» (для них легион), который начинается с «Чтобы использовать потоки, вы должны постоянно создавать их и ждать, пока они прекратятся». –

1

Насколько я знаю, такой метод не существует. Тем не менее, вы можете решить свою проблему, разрешив одному потоку работать навсегда и управлять повторениями, которые вам нужны в Test :: Method. Когда требуется новый расчет, основной цикл может уведомить Test :: Method, используя std :: condition_variable.

1

Технически говоря, mIsCalculating должно быть атомным значением, так что вы не получите проблем с использованием неатомной переменной в двух разных потоках. Помимо этого и предполагая, что это используется в «потоке пользовательского интерфейса», он будет вызываться только из одного потока, поэтому он должен быть приемлемым.

Существует также несколько альтернативных решений, таких как один поток, который работает вечно и подает данные в трубу, сообщение или использование события, чтобы сигнализировать «больше доступной работы и аналогичное событие для« вот результат » .»