多线程环境下队列操作之锁的教训
Posted 拙园
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程环境下队列操作之锁的教训相关的知识,希望对你有一定的参考价值。
之前一直在研究多线程环境下的编程方法,却很少实战体验,以至于我一提到多线程编程,我总是信心不足,又总是说不出到底哪里不明白。今天工程现场反馈了一个“老问题”,我一直担心的是DAServer的运行机制有什么我不明白的地方,DAS Toolkit中总有一部分是我没有仔细研究的,在我心中有阴影,所以工程出了问题我第一反应就是“会不会问题出在阴影里?”。结果,至今为止,我总结起来问题90%都是在自己编码部分。
我的DAServer中有一个需求,就是上送某个定点数据的速度不能太快,否则后台接收不过来。于是,我设计了一个类,包含了两条队列,其中一条队列是需要上送的数据,另一条队列是历史上曾经更新过的点记录及上送时间。每次组装报文时需要检查队列中是否有某个点,以及该点是否在1.5秒内“曾经上送过”。如果上送过就等下次再来检查,如果没有上送过则上送此信息并记录一下上送时间。
我在设计这个类时一直在关注逻辑,却忽略了这是一个多线程环境的应用——装入队列数据的线程和取出队列数据的线程是不同的。好吧,只能说明我们的应用场景太低端,多线程争用资源的场景不是太频繁,以至于我过了这么久才明白过来。这个类的设计代码如下:
1 struct SUpdateValue
2 {
3 int m_dataID; // 数据点号
4 DASVariant m_dataValue; // 数据值
5 SUpdateValue* next;
6
7 SUpdateValue() : m_dataID(0), next(NULL){ }
8 SUpdateValue(int id, DASVariant& val) : m_dataID(id), m_dataValue(val), next(NULL){ }
9 };
10
11 struct SUpdateTime
12 {
13 int dataID; // 数据点号
14 long lastSendTime; // 上次上送缓冲数据的时间戳(毫秒)
15 SUpdateTime* next;
16
17 SUpdateTime() : dataID(0), lastSendTime(0), next(NULL){}
18 };
19
20 class CDelayQueue
21 {
22 public:
23 CDelayQueue();
24 ~CDelayQueue();
25 bool EnqueFrame(int dataID, DASVariant& dataValue); // 向延迟队列中装入新数据
26 bool DequeFrame(int dataID, DASVariant& dataValue); // 从延迟队列中取出指定的数据
27 void DelayUpdateTime(int dataID); // 设定数据点的更新时间戳,一般用于防止过快上送
28 int GetFrameCount(void); // 获取延迟队列中的对象个数
29 string GetFrameList(void); // 获取对象名称列表,以逗号分隔
30
31 protected:
32 bool AskForUpdate(int dataID); // 申请上送新数据
33
34 private:
35 SUpdateValue* m_pHead; // 需要延迟发送的数据队列
36 SUpdateValue* m_pTail;
37 SUpdateTime* m_pTimeHead; // 已经发送的数据队列的历史记录(超时后失去记录)
38 };
实现部分如下:
1 CDelayQueue::CDelayQueue() : m_pHead(NULL), m_pTail(NULL), m_pTimeHead(NULL)
2 {
3 }
4
5 CDelayQueue::~CDelayQueue()
6 {
7 while (NULL != m_pHead)
8 {
9 SUpdateValue* p = m_pHead;
10 m_pHead = m_pHead->next;
11 delete p;
12 }
13
14 while (NULL != m_pTimeHead)
15 {
16 SUpdateTime* p = m_pTimeHead;
17 m_pTimeHead = m_pTimeHead->next;
18 delete p;
19 }
20 }
21
22 bool CDelayQueue::EnqueFrame(int dataID, DASVariant& dataValue)
23 {
24 SUpdateValue* pNew = new SUpdateValue(dataID, dataValue);
25 if (NULL == pNew)
26 {
27 return false;
28 }
29
30 if (NULL == m_pHead)
31 {
32 m_pHead = m_pTail = pNew;
33 }
34 else
35 {
36 m_pTail->next = pNew;
37 m_pTail = m_pTail->next;
38 }
39
40 return true;
41 }
42
43 bool CDelayQueue::DequeFrame(int dataID, DASVariant& dataValue)
44 {
45 if (NULL == m_pHead)
46 {
47 return false;
48 }
49
50 // 检查队列中是否存在该点
51 if (m_pHead->m_dataID == dataID)
52 {
53 if (AskForUpdate(dataID))
54 {
55 dataValue = m_pHead->m_dataValue;
56 SUpdateValue* pDel = m_pHead;
57 m_pHead = m_pHead->next;
58 delete pDel;
59 return true;
60 }
61 return false;
62 }
63
64 SUpdateValue* pPre = m_pHead;
65 SUpdateValue* pValue = m_pHead->next;
66 while (pValue != NULL)
67 {
68 if (pValue->m_dataID == dataID)
69 {
70 if (AskForUpdate(pValue->m_dataID))
71 {
72 dataValue = pValue->m_dataValue;
73 pPre->next = pValue->next;
74 delete pValue;
75 return true;
76 }
77 return false;
78 }
79 pPre = pValue;
80 pValue = pPre->next;
81 }
82
83 return false;
84 }
85
86 bool CDelayQueue::AskForUpdate(int dataID)
87 {
88 long curTime = GetTickCount();
89
90 // 检查是否在短时间内更新过该数据点
91 SUpdateTime* pList = m_pTimeHead;
92 while (pList)
93 {
94 if (pList->dataID == dataID)
95 {
96 if ((curTime - pList->lastSendTime) < 1500) // xiaoku
97 {
98 return false;
99 }
100 pList->lastSendTime = curTime;
101 return true;
102