设计一个 Map, 主线程高速插入和删除, 另一个线程定时删除 5 秒内没删除的数据

面对面试题中关于提高Map插入删除效率的问题,提出了结合ConcurrentHashMap和Queue的解决方案。使用ConcurrentHashMap确保线程安全,并通过自旋遍历Queue来定时删除5秒内未被删除的元素,以优化性能。探讨是否存在更优策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看到一个面试题目: 

设计一个 Map, 主线程高速插入和删除, 另一个线程定时删除 5 秒内没删除的数据

如果自旋 Map, 效率太低

引入 ConcurrentHashMap 和一个 Queue, 自旋遍历 Queue, 从头部开始移除

不知是否有更好的方法

/**
 * 设计一个 Map, 主线程高速插入和删除, 另一个线程定时删除 5 秒内没删除的数据
 * 高速: 想到 ConcurrentHashMap
 * 定时删除: 按时间顺序排列 -> 需要一个队列, 自旋遍历获取 key, 然后从 map remove, 
 * 直到队列里面值 插入时间 + 5秒 > 当前时间, 说明整个队列的值都还未过期
 */
public class QueueMap<K> {

	private volatile static QueueMap queueMap;

	/**
	 * 双重锁单例
	 */
	public static QueueMap instanceDouble() {
		if (queueMap == null) {
			synchronized (QueueMap.class) {
				if (queueMap == null) {
					queueMap = new QueueMap();
				}
			}
		}
		return queueMap;
	}

	/**
	 * 静态内部类单例
	 */
	public static QueueMap staticInner() {
		return QueueMapHolder.INSTANCE;
	}
	public static class QueueMapHolder {
		private static final QueueMap INSTANCE = new QueueMap();
	}

	private QueueMap() {
	}

	public static class SomeValue {
		
<think>我们已经在之前的回答中讨论了使用MFC定时器(SetTimer)实现100ms数据采集并写入表格的方法。但是,根据引用[1]引用[2]的内容,我们可以进一步优化。特别是引用[1]提到使用`timeSetEvent`(属于多媒体定时器)可以获得更高精度的定时,而引用[2]展示了使用普通定时器的基本方法。 由于用户要求100ms的定时采集,普通定时器(SetTimer)在Windows系统中最小精度通常为15ms(系统时钟分辨率),且消息队列可能延迟,因此对于100ms的定时,普通定时器基本可用。但如果需要更高精度(比如严格100ms,误差很小),则可以考虑多媒体定时器(`timeSetEvent`)。 然而,多媒体定时器需要多线程处理,因为它的回调函数在独立的线程中执行,因此需要注意线程安全。同时,在MFC中更新界面(如写入表格)必须在主线程中进行,因此需要跨线程通信。 下面提供两种方案: ### 方案一:使用普通定时器(SetTimer) 这是最简单的方法,适用于一般精度要求(100ms)。实现步骤与之前类似: 1. **在对话框类中声明定时器处理函数**: ```cpp // MyDialog.h class CMyDialog : public CDialog { // ... afx_msg void OnTimer(UINT_PTR nIDEvent); }; ``` 2. **在初始化时设置定时器**: ```cpp BOOL CMyDialog::OnInitDialog() { CDialog::OnInitDialog(); SetTimer(1, 100, NULL); // 100毫 // 初始化表格列 CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_DATALIST); pList->InsertColumn(0, _T("时间"), LVCFMT_LEFT, 100); pList->InsertColumn(1, _T("数值"), LVCFMT_LEFT, 100); return TRUE; } ``` 3. **定时器消息处理**: ```cpp void CMyDialog::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == 1) { // 采集数据 double data = ReadData(); // 假设的数据读取函数 CString strTime = CTime::GetCurrentTime().Format(_T("%H:%M:%S")); CString strValue; strValue.Format(_T("%.2f"), data); // 更新表格 CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_DATALIST); int nIndex = pList->InsertItem(0, strTime); // 插入新行,第一列为时间 pList->SetItemText(nIndex, 1, strValue); // 第二列为数值 // 可选:限制行数 if (pList->GetItemCount() > 100) { pList->DeleteItem(pList->GetItemCount()-1); } } CDialog::OnTimer(nIDEvent); } ``` 4. **销毁定时器**: ```cpp void CMyDialog::OnDestroy() { CDialog::OnDestroy(); KillTimer(1); } ``` ### 方案二:使用多媒体定时器(timeSetEvent) 对于更高精度的定时,使用多媒体定时器。但注意:多媒体定时器的回调函数不在主线程,因此不能直接操作UI。我们需要将数据采集后通过消息通知主线程更新界面。 1. **定义自定义消息结构**: ```cpp // MyDialog.h #define WM_UPDATE_DATA (WM_USER + 100) class CMyDialog : public CDialog { // ... afx_msg LRESULT OnUpdateData(WPARAM wParam, LPARAM lParam); // 定时器回调函数(静态函数,因为要求是C风格) static void CALLBACK TimeProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2); // 用于存储定时器句柄 MMRESULT m_timerId; // 用于线程同步的临界区(可选,如果多个线程同时访问共享资源) CRITICAL_SECTION m_cs; }; ``` 2. **初始化与启动多媒体定时器**: ```cpp BOOL CMyDialog::OnInitDialog() { CDialog::OnInitDialog(); // 初始化临界区(可选) InitializeCriticalSection(&m_cs); // 设置多媒体定时器:100ms周期,回调函数为TimeProc,传入this指针 m_timerId = timeSetEvent(100, 0, TimeProc, (DWORD_PTR)this, TIME_PERIODIC); if (m_timerId == 0) { AfxMessageBox(_T("定时器启动失败")); } // 初始化表格列(同上) // ... return TRUE; } ``` 3. **定时器回调函数**: ```cpp void CALLBACK CMyDialog::TimeProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) { CMyDialog* pDlg = (CMyDialog*)dwUser; if (pDlg) { // 采集数据(注意:这个函数在独立线程中执行) double data = pDlg->ReadData(); // 通过消息传递到主线程 ::PostMessage(pDlg->m_hWnd, WM_UPDATE_DATA, (WPARAM)data, 0); } } ``` 4. **处理自定义消息更新界面**: ```cpp BEGIN_MESSAGE_MAP(CMyDialog, CDialog) ON_MESSAGE(WM_UPDATE_DATA, OnUpdateData) // ... 其他消息 END_MESSAGE_MAP() LRESULT CMyDialog::OnUpdateData(WPARAM wParam, LPARAM lParam) { double data = (double)wParam; CString strTime = CTime::GetCurrentTime().Format(_T("%H:%M:%S")); CString strValue; strValue.Format(_T("%.2f"), data); CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_DATALIST); int nIndex = pList->InsertItem(0, strTime); pList->SetItemText(nIndex, 1, strValue); // 限制行数 if (pList->GetItemCount() > 100) { pList->DeleteItem(pList->GetItemCount()-1); } return 0; } ``` 5. **销毁多媒体定时器**: ```cpp void CMyDialog::OnDestroy() { if (m_timerId) { timeKillEvent(m_timerId); m_timerId = 0; } DeleteCriticalSection(&m_cs); // 销毁临界区 CDialog::OnDestroy(); } ``` ### 两种方案的比较 - **普通定时器(SetTimer)**: - 优点:简单,直接在主线程中处理,无需考虑线程安全。 - 缺点:精度受系统消息队列影响,如果UI线程繁忙,可能导致定时器消息延迟。 - **多媒体定时器(timeSetEvent)**: - 优点:精度高(可达到1ms精度),独立线程执行,不受UI线程影响。 - 缺点:需要多线程编程,不能直接操作UI,必须通过消息传递到主线程;需要额外处理线程同步(如临界区)以避免数据竞争。 ### 性能优化建议 1. **避免在定时器处理中执行耗时操作**:如果数据采集本身耗时较长(如超过100ms),则无论哪种方案都会导致问题。此时,建议将数据采集放在工作线程中,定时器只负责触发采集。 2. **界面更新优化**:频繁更新表格可能导致界面卡顿。可以使用`SetRedraw(FALSE/TRUE)`来减少重绘次数,或者使用虚拟列表控件(对于大量数据)。 3. **数据缓冲**:如果采集频率很高,可以考虑先将数据存入缓冲区,然后定时批量更新界面(比如每1更新一次界面,但采集仍为100ms一次)。 ### 相关问题 1. 如何提高MFC定时器的精度? 2. MFC中如何实现多线程数据采集? 3. 如何将采集的数据实时保存到文件? 4. 如何防止界面在频繁更新时卡顿? 5. 多媒体定时普通定时器的主要区别是什么? 根据实际需求选择方案。如果100ms的定时要求不是非常严格(比如允许±10ms的误差),使用普通定时器即可;如果需要更精确的定时,则使用多媒体定时器。</think>### MFC实现100ms定时数据采集与实时记录方案 在MFC中实现高精度定时数据采集实时记录,推荐结合多媒体定时器(`timeSetEvent`)线程安全的数据处理机制。以下是分步实现方案: --- ### 一、核心实现方法 #### 1. **使用多媒体定时器(高精度定时)** ```cpp // MyDialog.h class CMyDialog : public CDialog { private: MMRESULT m_timerId; // 定时器句柄 static void CALLBACK TimerProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2); void CollectData(); // 数据采集函数 }; // MyDialog.cpp BOOL CMyDialog::OnInitDialog() { // 设置100ms精确定时器(TIME_PERIODIC表示周期性触发) m_timerId = timeSetEvent( 100, // 100ms延迟 0, // 最高精度 TimerProc, // 回调函数 (DWORD_PTR)this, // 传递this指针 TIME_PERIODIC // 周期性定时器 ); return TRUE; } // 定时器回调函数(静态成员) void CALLBACK CMyDialog::TimerProc(UINT, UINT, DWORD dwUser, DWORD, DWORD) { CMyDialog* pThis = reinterpret_cast<CMyDialog*>(dwUser); pThis->CollectData(); // 调用数据采集 } ``` #### 2. **数据采集与表格写入** ```cpp void CMyDialog::CollectData() { // 1. 采集数据(示例) double sensorValue = ReadSensor(); // 实际采集函数 CString timestamp = CTime::GetCurrentTime().Format("%H:%M:%S.%f"); // 2. 线程安全地更新UI PostMessage(WM_UPDATE_GRID, (WPARAM)new DataPair(timestamp, sensorValue)); } // 消息处理函数(主线程执行) afx_msg LRESULT CMyDialog::OnUpdateGrid(WPARAM wParam, LPARAM) { DataPair* pData = reinterpret_cast<DataPair*>(wParam); // 3. 更新表格控件 CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_DATALIST); int nRow = pList->InsertItem(0, pData->timestamp); pList->SetItemText(nRow, 1, FormatDouble(pData->value)); delete pData; // 释放内存 return 0; } // 数据传递结构体 struct DataPair { CString timestamp; double value; DataPair(CString ts, double v) : timestamp(ts), value(v) {} }; ``` #### 3. **表格初始化与销毁** ```cpp // 初始化表格列 BOOL CMyDialog::OnInitDialog() { // ... CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_DATALIST); pList->InsertColumn(0, _T("时间戳"), LVCFMT_LEFT, 150); pList->InsertColumn(1, _T("数值"), LVCFMT_RIGHT, 100); return TRUE; } // 销毁定时器 void CMyDialog::OnDestroy() { if(m_timerId) timeKillEvent(m_timerId); CDialog::OnDestroy(); } ``` --- ### 二、关键优化技术 1. **线程安全机制** - 使用`PostMessage`将数据定时线程传递到主线程 - 避免直接操作UI控件(违反MFC线程规则) 2. **性能优化** ```cpp // 批量更新时禁用重绘 pList->SetRedraw(FALSE); // ... 插入多行数据 ... pList->SetRedraw(TRUE); pList->Invalidate(); ``` 3. **数据缓冲队列** ```cpp // 使用线程安全队列 CAtlList<DataPair> m_dataQueue; CCriticalSection m_cs; // 临界区 // 定时线程 void CMyDialog::CollectData() { CSingleLock lock(&m_cs, TRUE); m_dataQueue.AddTail(DataPair(...)); } // UI定时器批量处理 void CMyDialog::OnProcessQueue() { CSingleLock lock(&m_cs, TRUE); while(!m_dataQueue.IsEmpty()) { DataPair data = m_dataQueue.RemoveHead(); // 更新表格 } } ``` --- ### 三、方案对比 | **方法** | **精度** | **线程安全** | **适用场景** | |----------------|----------|--------------|----------------------| | `SetTimer` | ≈15ms | 主线程安全 | 普通定时需求 | | `timeSetEvent` | 1ms | 需跨线程同步 | 高精度实时采集[^1] | | 工作线程+定时 | 可调 | 复杂 | 耗时采集操作[^3] | --- ### 四、完整工作流程 ```mermaid sequenceDiagram participant Timer as 多媒体定时器 participant Worker as 采集线程 participant Main as 主线程 participant UI as 表格控件 Timer->>Worker: 100ms触发 Worker->>Worker: 执行数据采集 Worker->>Main: PostMessage(带数据) Main->>UI: 插入新行 UI->>UI: 更新显示 ``` --- ### 五、注意事项 1. **精度限制**:Windows非实时系统,实际精度≈1-5ms 2. **资源释放**:必须调用`timeKillEvent`释放定时器 3. **数据丢失**:高频率采集时使用队列缓冲 4. **异常处理**:添加采集设备断连的重连机制 5. **内存管理**:`PostMessage`传递的数据需手动释放 > **提示**:对于需要精确时间戳的场景,建议使用`QueryPerformanceCounter`获取高精度时间: > ```cpp > LARGE_INTEGER freq, start; > QueryPerformanceFrequency(&freq); > QueryPerformanceCounter(&start); > double timestamp = (double)start.QuadPart / freq.QuadPart; > ``` --- ### 相关问题 1. 如何实现MFC与外部传感器的串口通信? 2. 多媒体定时器与普通定时器的性能差异如何量化? 3. 如何处理高频率数据采集时的界面卡顿问题? 4. 如何将采集的数据实时保存到CSV文件? 5. MFC中如何实现动态滚动的实时数据曲线图?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值