[英]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.