понедельник, 26 сентября 2011 г.

Контролируемый выход из цикла в потоке

В своей деятельности я часто использую потоки. Как правило, из библиотеки boost::thread. На пару с boost::bind возможности получаются впечатляющие. Если поток однократно выполняет какое-либо действие, и сам завершается -- всё хорошо. Но зачастую требуется, чтобы поток выполнял это действие в цикле, через заданный временной интервал, и был способен получать уведомления о завершении своей работы от основного потока приложения. Для подобных целей я использую следующий механизм, основанный на совместном использовании boost::shared_mutex и boost::shared_lock:
#define BOOST_THREAD_USE_LIB
//-------------------------------------------------------------------------
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
//-------------------------------------------------------------------------
#include <string>
#include <iostream>
//-------------------------------------------------------------------------
/*
 g++ -Wall -o ./main ./main.cpp -lboost_thread
*/
//-------------------------------------------------------------------------
class Example
{
private:
 typedef boost::shared_mutex WaiterSharedMutex;
 typedef boost::shared_lock<WaiterSharedMutex> WaiterSharedLock;
 typedef boost::defer_lock_t DeferredLock;
 boost::thread workThread;
 WaiterSharedMutex waiterMutex;
 boost::posix_time::time_duration sleepTime;
 void WorkThread(void)
 {
  while(true)
  {
   WaiterSharedLock wsl(waiterMutex,DeferredLock());
   if(wsl.timed_lock(boost::get_system_time() + sleepTime))
   {
    std::cerr << "Forced exit from thread" << std::endl;
    return;
   }
   std::cerr << "Work of thread" << std::endl;
  }
  return;
 }
public:
 Example(std::size_t timeToSleep) : sleepTime(boost::posix_time::milliseconds(timeToSleep))
 {
  waiterMutex.lock();
  return;
 }
 ~Example(void)
 {
  waiterMutex.unlock();
  workThread.join();
  return;
 }
 bool Start(void)
 {
  bool result = false;
  if(workThread.joinable() == false)
  {
   waiterMutex.try_lock();
   workThread = boost::thread(boost::bind(&Example::WorkThread,this));
   result = true;
  }
  return result;
 }
 bool Stop(void)
 {
  bool result = false;
  waiterMutex.unlock();
  if(workThread.joinable() == true)
  {
   workThread.join();
  }
  return result;
 }
};
//-------------------------------------------------------------------------
int main(int argc,char** argv)
{
 std::string input;
 Example example(50000);
 while(true)
 {
  input.clear();
  std::cout << ":> ";
  std::cin >> input;
  if(!input.compare("quit"))
  {
   break;
  }
  else if(!input.compare("start"))
  {
   example.Start();
  }
  else if(!input.compare("stop"))
  {
   example.Stop();
  }
 }
 return 0;
}
Временная задержка выполнения потока обеспечивается ожиданием на mutex-е функцией timed_lock, а возможность немедленного завершения потока тем, что как сам mutex, так и его блокировка являются разделяемыми (shared_mutex и shared_lock). У разделяемого mutex-а есть две возможных блокировки: эксклюзивная, и разделяемая. Если на mutex-е эксклюзивная блокировка -- заблокировать его разделяемой будет невозможно. Обратное тоже верно. Но эксклюзивную блокировку может создавать только один поток, а разделяемую -- несколько. В представленном примере на mutex ставится эксклюзивная блокировка в конструкторе класса (строка 40), и перед попыткой пересоздать поток (строка 54). Поток (потоки), которому требуется выполнять в цикле некую функцию, пытается получить разделяемую блокировку на mutex (строка 28). Завершиться успехом это может только в случае, если основной поток отпустит эксклюзивную блокировку (строка 63, и вызов функции Stop в деструкторе класса). После чего требуется лишь дождаться завершения потока (потоков) функцией join.