我有一个Timer类,它调用一个传递给create
的函数,它循环并在任何时间内休眠。
我遇到的问题是当试图从传递的函数中杀死那个计时器时我在pThis->Stop()
即使我访问全局类对象
,我似乎也遇到了同样的崩溃类别:
class CTimer
{
public:
CTimer()
:_running(false)
{}
~CTimer() {
if (_running.load(std::memory_order_acquire)) {
Stop();
};
}
void Stop()
{
_running.store(false, std::memory_order_release);
if (_thread.joinable())
_thread.join();
}
template <typename F, typename... A>
void Start(int interval, F func, A&&... args)
{
if (_running.load(std::memory_order_acquire))
Stop();
_running.store(true, std::memory_order_release);
_thread = std::thread([this, interval, func, &args...]()
{
while (_running.load(std::memory_order_acquire))
{
func(this, std::forward<A>(args)...);
std::this_thread::sleep_for(std::chrono::milliseconds(interval));
}
});
}
bool Running() const noexcept {
return (_running.load(std::memory_order_acquire) &&
_thread.joinable());
}
private:
std::atomic<bool> _running;
std::thread _thread;
};
全局:
CTimer Timer;
主题:
void TimerThread(CTimer* pThis, HWND hwnd)
{
// my code in side here
// everything works fine till i try to stop within this thread
// crash here
pThis->Stop();
}
这样称呼:
Timer.Start(2000, TimerThread, hwnd);
答案 0 :(得分:1)
你正试图从内部join()
线程 - 这是一个问题。
看看你是否可以在滴答功能之外停止计时器。如果你不能,那么考虑让tick函数返回bool
,看它是否应该继续运行。
无论如何,线程应始终加入,并始终来自不同的线程。
答案 1 :(得分:0)
我接受了代码并删除了hwnd
- 替换为nullptr并找到了以下内容。
if (_thread.joinable())
_thread.join();
此代码在线程函数TimerThread
中不起作用,因此当调用Stop时,会出现如cppreference : thread join所述抛出的死锁异常
我认为修复应该只是在函数中将_running设置为false。但目前尚不清楚代码中究竟需要什么。
全局计时器破坏也可能发生得太晚 - 当全局变量被破坏时,这就像C ++运行时正在卸载一样,此时线程:: join可能会失败。
间隔将是更有效的计时机制,您可以在此之前测量时间,然后再读取它。
std :: chrono具有读取时钟和获取时序的方法。 cppreference: chrono
auto start = std::chrono::high_resolution_clock::now();
/* do some stuff */
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end-start;
答案 2 :(得分:0)
正如其他人所指出的那样,调用join()
的线程本身就是一个保证死锁 - 尽管应该导致异常。您似乎是在Windows上开发 - 所以可能使用MSVC - 您可能想要检查您的库的标准合规性,因为直到最近它还没有。
这里还有其他一些非常糟糕的代码气味:
目前CTimer
的析构函数阻塞最多interval
毫秒。这是一个糟糕的界面设计,因为它是违反直觉的。您可能很容易发现自己长时间阻塞主线程,而且如果func()
阻塞资源 - 或者试图销毁CTimer
对象,则非常有可能出现死锁。这是为将来存储的代码维护危险。
std::thread::join()
可以抛出异常。这是std::terminate
异常或未定义行为的快速路径。
this
指针
就是不要这样做
虽然 - 我认为 - CTimer
的生命时间及其产生的线程目前是对齐的,但这是一个代码维护定时炸弹。
更喜欢通过捕获CTimer
来解除线程和std::weak_ptr<CTimer>
的生命周期:
class CTimer : public std::enable_shared_from_this<CTimer>
{
....
std::weak_ptr<CTimer> weakSelf{shared_from_this()};
_thread = std::thread([weakSelf, interval, func, &args...]()
{
简单地在析构函数中分离线程
~CTimer() {
if (_running.load(std::memory_order_acquire)) {
_thread.detach();
};
计时器线程中的循环变为:
while (1)
{
auto strongSelf = weakSelf.lock();
if (!strongSelf);
return;
if (!strongSelf->_running.load(std::memory_order_acquire))
return;
func(this, std::forward<A>(args)...);
std::this_thread::sleep_for(std::chrono::milliseconds(interval));
}
这也有解决析构函数中阻塞问题的额外好处。当CTimer
对象被销毁时,线程继续,最终超时并发现其创建者不再存在,并且干净地退出。
func
使用此接口设计,func
绑定到非静态类成员函数非常容易。同样,未能控制对象的相对生命周期。一个解决方案(在调用者处)是在lambda中捕获std::weak_ptr
- 或者您可以将接口更改为不太通用以强制执行此操作。