[英]Can POSIX timers safely modify C++ STL objects?
我正在尝试为Linux上的POSIX计时器系统编写C ++“包装器”,以便我的C ++程序可以使用系统时钟为某些事情(例如,等待消息通过网络到达)设置超时时间,而无需处理POSIX的丑陋C接口。 它似乎大部分时间都可以工作,但是偶尔我的程序在成功运行几分钟后会出现段错误。 问题似乎是我的LinuxTimerManager对象(或其成员对象之一)的内存已损坏,但是不幸的是,如果我在Valgrind下运行该程序,该问题将拒绝出现,因此我一直盯着我的代码尝试计算找出问题所在。
这是我的计时器包装器实现的核心:
LinuxTimerManager.h :
namespace util {
using timer_id_t = int;
class LinuxTimerManager {
private:
timer_id_t next_id;
std::map<timer_id_t, timer_t> timer_handles;
std::map<timer_id_t, std::function<void(void)>> timer_callbacks;
std::set<timer_id_t> cancelled_timers;
friend void timer_signal_handler(int signum, siginfo_t* info, void* ucontext);
public:
LinuxTimerManager();
timer_id_t register_timer(const int delay_ms, std::function<void(void)> callback);
void cancel_timer(const timer_id_t timer_id);
};
void timer_signal_handler(int signum, siginfo_t* info, void* ucontext);
}
LinuxTimerManager.cpp :
namespace util {
LinuxTimerManager* tm_instance;
LinuxTimerManager::LinuxTimerManager() : next_id(0) {
tm_instance = this;
struct sigaction sa = {0};
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_signal_handler;
sigemptyset(&sa.sa_mask);
int success_flag = sigaction(SIGRTMIN, &sa, NULL);
assert(success_flag == 0);
}
void timer_signal_handler(int signum, siginfo_t* info, void* ucontext) {
timer_id_t timer_id = info->si_value.sival_int;
auto cancelled_location = tm_instance->cancelled_timers.find(timer_id);
//Only fire the callback if the timer is not in the cancelled set
if(cancelled_location == tm_instance->cancelled_timers.end()) {
tm_instance->timer_callbacks.at(timer_id)();
} else {
tm_instance->cancelled_timers.erase(cancelled_location);
}
tm_instance->timer_callbacks.erase(timer_id);
timer_delete(tm_instance->timer_handles.at(timer_id));
tm_instance->timer_handles.erase(timer_id);
}
timer_id_t LinuxTimerManager::register_timer(const int delay_ms, std::function<void(void)> callback) {
struct sigevent timer_event = {0};
timer_event.sigev_notify = SIGEV_SIGNAL;
timer_event.sigev_signo = SIGRTMIN;
timer_event.sigev_value.sival_int = next_id;
timer_t timer_handle;
int success_flag = timer_create(CLOCK_REALTIME, &timer_event, &timer_handle);
assert(success_flag == 0);
timer_handles[next_id] = timer_handle;
timer_callbacks[next_id] = callback;
struct itimerspec timer_spec = {0};
timer_spec.it_interval.tv_sec = 0;
timer_spec.it_interval.tv_nsec = 0;
timer_spec.it_value.tv_sec = 0;
timer_spec.it_value.tv_nsec = delay_ms * 1000000;
timer_settime(timer_handle, 0, &timer_spec, NULL);
return next_id++;
}
void LinuxTimerManager::cancel_timer(const timer_id_t timer_id) {
if(timer_handles.find(timer_id) != timer_handles.end()) {
cancelled_timers.emplace(timer_id);
}
}
}
当我的程序崩溃时,segfault总是来自timer_signal_handler()
,通常是行tm_instance->timer_callbacks.erase(timer_id)
或tm_instance->timer_handles.erase(timer_id)
。 实际的段错误是从std::map
实现的深处(即stl_tree.h
) stl_tree.h
。
我的内存损坏可能是由于修改同一LinuxTimerManager的不同计时器信号之间的竞争状况引起的吗? 我以为一次只传递一个计时器信号,但是也许我误解了手册页。 通常,让Linux信号处理程序修改一个复杂的C ++对象(如std::map
通常是不安全的吗?
该信号可能发生在例如malloc
或free
的中间,因此大多数使用容器执行有趣操作的调用可能导致在其数据结构处于任意状态时重新输入内存分配支持。 (正如注释中指出的那样,大多数函数在异步信号处理程序中都不安全调用malloc
和free
只是示例。)以这种方式重新进入组件会导致相当多的任意失败。
如果没有在库中进行任何操作的过程中阻塞整个过程的信号,就无法确保库不受此行为的影响。 这样做非常昂贵,无论是在管理信号掩码的开销还是在时间量上都将被阻塞。 (必须用于整个过程,因为信号处理程序不应在锁上阻塞。如果处理信号的线程调用了受互斥锁保护的库,而另一个线程持有信号处理程序需要的互斥锁,则该处理程序将阻塞。在这种情况下很难避免死锁。)
解决此问题的设计通常具有一个线程,该线程侦听特定事件,然后进行处理。 您必须使用信号量在线程和信号处理程序之间进行同步。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.