[英]Race condition between terminating worker threads and main thread
我在從主線程終止工作線程時遇到問題。 到目前為止,我嘗試的每種方法都會導致爭用狀況或死鎖。
工作線程存儲在名為ThreadPool的類的內部類中,ThreadPool使用unique_ptr維護這些WorkerThreads的向量。
這是我的ThreadPool的標題:
class ThreadPool
{
public:
typedef void (*pFunc)(const wpath&, const Args&, Global::mFile_t&, std::mutex&, std::mutex&); // function to point to
private:
class WorkerThread
{
private:
ThreadPool* const _thisPool; // reference enclosing class
// pointers to arguments
wpath _pPath; // member argument that will be modifyable to running thread
Args * _pArgs;
Global::mFile_t * _pMap;
// flags for thread management
bool _terminate; // terminate thread
bool _busy; // is thread busy?
bool _isRunning;
// thread management members
std::mutex _threadMtx;
std::condition_variable _threadCond;
std::thread _thisThread;
// exception ptr
std::exception_ptr _ex;
// private copy constructor
WorkerThread(const WorkerThread&): _thisPool(nullptr) {}
public:
WorkerThread(ThreadPool&, Args&, Global::mFile_t&);
~WorkerThread();
void setPath(const wpath); // sets a new task
void terminate(); // calls terminate on thread
bool busy() const; // returns whether thread is busy doing task
bool isRunning() const; // returns whether thread is still running
void join(); // thread join wrapper
std::exception_ptr exception() const;
// actual worker thread running tasks
void thisWorkerThread();
};
// thread specific information
DWORD _numProcs; // number of processors on system
unsigned _numThreads; // number of viable threads
std::vector<std::unique_ptr<WorkerThread>> _vThreads; // stores thread pointers - workaround for no move constructor in WorkerThread
pFunc _task; // the task threads will call
// synchronization members
unsigned _barrierLimit; // limit before barrier goes down
std::mutex _barrierMtx; // mutex for barrier
std::condition_variable _barrierCond; // condition for barrier
std::mutex _coutMtx;
public:
// argument mutex
std::mutex matchesMap_mtx;
std::mutex coutMatch_mtx;
ThreadPool(pFunc f);
// wake a thread and pass it a new parameter to work on
void callThread(const wpath&);
// barrier synchronization
void synchronizeStartingThreads();
// starts and synchronizes all threads in a sleep state
void startThreads(Args&, Global::mFile_t&);
// terminate threads
void terminateThreads();
private:
};
到目前為止,我真正遇到的問題是從主線程調用TerminateThreads()時會導致死鎖或競爭狀態。
當我將_terminate標志設置為true時,在線程有機會喚醒並終止之前,main可能已經退出作用域並破壞了所有互斥對象。 實際上,我已經多次崩潰(控制台窗口顯示:互斥鎖在忙碌時被破壞)
如果我在notify_all()線程之后添加thread.join(),則線程有可能在連接發生之前終止,從而導致無限死鎖,因為與終止線程的連接將無限期掛起程序。
如果我分離-與上述相同,但導致程序崩潰
如果我改為使用while(WorkerThread.isRunning())Sleep(0); 程序可能會崩潰,因為主線程可能在WorkerThread到達最后一個關閉括號之前退出。
在所有工作線程安全終止之前,我不確定還可以采取什么措施來停止主線程。 同樣,即使在線程和main中使用try-catch,也不會捕獲任何異常。 (我嘗試過的所有方法都會導致程序崩潰)
在工作線程完成之前,我該怎么做才能停止主線程?
以下是主要功能的實現:
終止單個工作線程
void ThreadPool::WorkerThread::terminate()
{
_terminate = true;
_threadCond.notify_all();
_thisThread.join();
}
實際的ThreadLoop
void ThreadPool::WorkerThread::thisWorkerThread()
{
_thisPool->synchronizeStartingThreads();
try
{
while (!_terminate)
{
{
_thisPool->_coutMtx.lock();
std::cout << std::this_thread::get_id() << " Sleeping..." << std::endl;
_thisPool->_coutMtx.unlock();
_busy = false;
std::unique_lock<std::mutex> lock(_threadMtx);
_threadCond.wait(lock);
}
_thisPool->_coutMtx.lock();
std::cout << std::this_thread::get_id() << " Awake..." << std::endl;
_thisPool->_coutMtx.unlock();
if(_terminate)
break;
_thisPool->_task(_pPath, *_pArgs, *_pMap, _thisPool->coutMatch_mtx, _thisPool->matchesMap_mtx);
_thisPool->_coutMtx.lock();
std::cout << std::this_thread::get_id() << " Finished Task..." << std::endl;
_thisPool->_coutMtx.unlock();
}
_thisPool->_coutMtx.lock();
std::cout << std::this_thread::get_id() << " Terminating" << std::endl;
_thisPool->_coutMtx.unlock();
}
catch (const std::exception&)
{
_ex = std::current_exception();
}
_isRunning = false;
}
終止所有工作線程
void ThreadPool::terminateThreads()
{
for (std::vector<std::unique_ptr<WorkerThread>>::iterator it = _vThreads.begin(); it != _vThreads.end(); ++it)
{
it->get()->terminate();
//it->get()->_thisThread.detach();
// if thread threw an exception, rethrow it in main
if (it->get()->exception() != nullptr)
std::rethrow_exception(it->get()->exception());
}
}
最后,正在調用線程池的函數(掃描函數在main上運行)
// scans a path recursively for all files of selected extension type, calls thread to parse file
unsigned int Functions::Scan(wpath path, const Args& args, ThreadPool& pool)
{
wrecursive_directory_iterator d(path), e;
unsigned int filesFound = 0;
while ( d != e )
{
if (args.verbose())
std::wcout << L"Grepping: " << d->path().string() << std::endl;
for (Args::ext_T::const_iterator it = args.extension().cbegin(); it != args.extension().cend(); ++it)
{
if (extension(d->path()) == *it)
{
++filesFound;
pool.callThread(d->path());
}
}
++d;
}
std::cout << "Scan Function: Calling TerminateThreads() " << std::endl;
pool.terminateThreads();
std::cout << "Scan Function: Called TerminateThreads() " << std::endl;
return filesFound;
}
我會再次重復這個問題:在工作線程完成之前,我該怎么做才能停止主線程?
我沒有出現線程終止和連接的問題。
加入線程只不過是要等到給定線程終止后才行,所以這很簡單。 如果線程已經完成執行, join
將立即返回。
因此,您只想像在代碼中已經做過的那樣,在terminate
調用期間加入每個線程。
注意:如果您剛剛終止的線程具有活動的exception_ptr
則當前您會立即拋出任何exception_ptr
。 這可能導致未連接的線程。 處理這些異常時,您必須牢記這一點
更新:查看您的代碼后,我看到一個潛在的錯誤:當發生虛假喚醒時, std::condition_variable::wait()
可以返回。 如果是這種情況,您將在上次使用的路徑上再次工作,從而導致錯誤的結果。 如果已經添加了新工作,則應該為新工作設置一個標志,並且_threadCond.wait(lock)
行應處於檢查標志和_terminate
。 不過,不確定是否可以解決您的問題。
問題有兩個:
syncnizeStartingThreads()有時會阻塞1或2個線程,等待一切順利(while(some_condition)barrierCond.wait(lock)中的問題。該條件有時永遠不會評估為true。阻止問題。
第二個問題是工作線程有可能進入_threadMtx,並在進入_threadCond.wait()之前調用notify_all,因為已經調用了notify,所以線程將永遠等待。
即。
{
// terminate() is called
std::unique_lock<std::mutex> lock(_threadMtx);
// _threadCond.notify_all() is called here
_busy = false;
_threadCond.wait(lock);
// thread is blocked forever
}
令人驚訝的是,將此互斥鎖鎖定在terminate()中並沒有阻止這種情況的發生。
通過在_threadCond.wait()中添加30ms超時來解決此問題
另外,在任務開始之前添加了檢查,以確保不會再次處理同一任務。
現在,新代碼如下所示:
thisWorkerThread
_threadCond.wait_for(lock, std::chrono::milliseconds(30)); // hold the lock a max of 30ms
// after the lock, and the termination check
if(_busy)
{
Global::mFile_t rMap = _thisPool->_task(_pPath, *_pArgs, _thisPool->coutMatch_mtx);
_workerMap.element.insert(rMap.element.begin(), rMap.element.end());
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.