多線程環境下隊列操作之鎖的教訓
阿新 • • 發佈:2017-12-08
ring 列數 str 列操作 多線程編程 locker 操作 類的設計 queue
之前一直在研究多線程環境下的編程方法,卻很少實戰體驗,以至於我一提到多線程編程,我總是信心不足,又總是說不出到底哪裏不明白。今天工程現場反饋了一個“老問題”,我一直擔心的是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 }
103 pList = pList->next;
104 }