1. 程式人生 > >多線程環境下隊列操作之鎖的教訓

多線程環境下隊列操作之鎖的教訓

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     }
105 
106     // 如果記錄中沒有目標點,則創建歷史記錄
107     if (NULL == pList)
108     {
109         pList = new SUpdateTime();
110         pList->dataID = dataID;
111         pList->lastSendTime = curTime;
112         pList->next = m_pTimeHead;
113         m_pTimeHead = pList;
114     }
115 
116     return true;
117 }
118 
119 void CDelayQueue::DelayUpdateTime(int dataID)
120 {
121     long curTime = ::GetTickCount();
122     SUpdateTime* pList = m_pTimeHead;
123     while (pList)
124     {
125         if (pList->dataID == dataID)
126         {
127             pList->lastSendTime = curTime - 500;
128             return ;
129         }
130         pList = pList->next;
131     }
132 
133     // 如果記錄中沒有目標點,則創建歷史記錄
134     if (NULL == pList)
135     {
136         pList = new SUpdateTime();
137         pList->dataID = dataID;
138         pList->lastSendTime = curTime - 500;
139         pList->next = m_pTimeHead;
140         m_pTimeHead = pList;
141     }
142 }
143 
144 int CDelayQueue::GetFrameCount()
145 {
146     if (NULL == m_pHead)
147     {
148         return 0;
149     }
150 
151     int count = 1;
152     SUpdateValue* p = m_pHead;
153     while(p != m_pTail)
154     {
155         ++count;
156         p = p->next;
157     }
158     return count;
159 }
160 
161 string CDelayQueue::GetFrameList()
162 {
163     string strNameList("");
164     if (NULL == m_pHead)
165     {
166         return strNameList;
167     }
168     SUpdateValue* p = m_pHead;
169     char szData[16];
170     sprintf_s(szData, 16, "%d", p->m_dataID);
171     strNameList += szData;
172     while(p != m_pTail)
173     {
174         sprintf_s(szData, 16, "%d", p->next->m_dataID);
175         strNameList += ";";
176         strNameList += szData;
177         p = p->next;
178     }
179 
180     return strNameList;
181 }
技術分享圖片

  最關鍵兩個接口是EnqueFrame() 和 DequeFrame() ,分別是裝入隊列和從隊列中取數。都是在操作隊列,如果是不同的線程,怎麽能不考慮線程互斥的問題呢?好吧,迅速引入LockerGuard。

  這個失敗的例子放在這裏警示一下自己!

多線程環境下隊列操作之鎖的教訓