這幾天晚上群裡一朋友有償叫我把他的程序弄穩定,因為是現場管理項目,需要做到無人職守,所以即使是客戶端,也不能經常down機,因為之前對他的程序有過一個晚上的實地查看,基本流程已經有個大概的了解,我就接下來了。
剛開始的時候, 程序運行不到一個上午,內存暴漲,有時幾個小時就掛了,這個那天晚上發現了,找了半天發現一處加載圖片的TMemoryStream沒有釋放。
沒想到接下來還有很多的坑需要填。
一個個解決吧,看看有多少坑!
第一天:開始他跟我說他的連接數據庫老是超出數量,會導致程序不能處理任務,當天晚上就用diocp3中的BaseQueue做了個輕便的ADO連接池,因為baseQueue做了大量的測試,所以這個ado連接池經過簡單的測試就算OK了,接下來就大量的苦力工作了。
第二天:說連接池換上後,連接問題沒有了,可是線程池不能處理任務了。他之前使用的一個叫uThreadPool單元,代碼好復雜。我推薦他使用QWokers,因為qwokers我一直在使用,很穩定了,任務的投遞也很簡單,後來一想,他是D7,沒辦法用這麼好的類庫,沒辦法,只好iocpTask上了。iocpTask diocp3中的一個任務投遞的庫, 是個類似qworker的庫,不過只有任務投遞和執行的功能,比起qworker來說太簡單了,不過用iocpEngine穩定性應該也是不錯的了。換上後,我接著對iocpTask做了寫修改,加強了調試信息,能夠很方便的看到工作線程的當前狀態,和任務執行的狀態。
因為他說線程池有問題,我估計肯定是卡在哪個任務了,到時候卡死的時候一看就明白了。
下面是iocpTask輸出的狀態信息,
post counter:10000001 response counter:10000001 error counter:0 active : True, worker count: 5 ----------------------- woker 1 -------------------- thread id: 9664, response count: 1719388 busying:False, waiting:True, reserved:True request state info: runInMainThread: False, done: True, time(ms): 0 ----------------------- woker 2 -------------------- thread id: 6680, response count: 2010817 busying:False, waiting:True, reserved:True request state info: runInMainThread: False, done: True, time(ms): 0 ----------------------- woker 3 -------------------- thread id: 4956, response count: 1965637 busying:False, waiting:True, reserved:False request state info: runInMainThread: False, done: True, time(ms): 0 ----------------------- woker 4 -------------------- thread id: 8304, response count: 2641407 busying:False, waiting:True, reserved:False request state info: runInMainThread: False, done: True, time(ms): 0 ----------------------- woker 5 -------------------- thread id: 9528, response count: 1662752 busying:False, waiting:True, reserved:False request state info: runInMainThread: False, done: True, time(ms): 0
因為是gitHub上面的項目所以我用了E文注釋和E文信息,中式英文,都懂的,稍微解釋下,
post counter:10000001 //代表投遞的任務數
response counter:10000001 //響應數
error counter:0 //投遞失敗數量
下面是工作線程的一些信息,有線程ID, 響應處理的任務數量,
下面的狀態比較關鍵
busying 的true的話就是代表正在執行任務,wating是在等待狀態, reserved是代表常駐工作線程
如果busying為true,會跟著出現當前正在執行的任務信息,如果任務都在執行這樣線程池達到最多的線程數,就不能處理新的任務了。導致了他程序發生的情況。這樣可以根據任務的信息找到對應的過程,去縮小范圍查詢問題。
request state info: 如果任務有備注信息,會顯示在這裡
runInMainThread:是否在主線程執行的任務,done:任務是否完成, time(ms): 是任務耗用的時間。
有了iocpTask這個坑很快找到了。
發現原來臨界用了很多,導致了任務死鎖…,
明天要接著填臨界的坑了。
第三天:他的程序臨界使用泛濫,當前進入不了臨界,導致任務掛起,但是並不是當前臨界的問題,是因為有其他任務進入臨界沒有退出。所以要找出前面的臨界,把臨界類改造了下。
可以看到臨界的當前信息,我把iocpLocker也進行了相應的升級
function TIocpLocker.getDebugINfo: String; begin Result := Format('%s: busycount:%d, try:%s, enter:%s', [self.FName, GetEnterCount, FTryEnterInfo, FEnterInfo]); end;
可以獲取臨界的名稱,進入嘗試進入臨界的線程個數,嘗試進入臨界的信息和已經進入臨界的信息。
有了這個數據就可以看到死鎖的臨界已經進入的臨界信息就是代表造成死鎖的元凶了。
第四天:因為線程裡面對UI的訪問過多,可以用iocpTask在線程中對 UI的訪問部分,通過投遞任務的方式投遞到主線程中完成工作,這也是個苦力。
認真的對他的程序做了改進,並講解了多線程編程需要注意的地方<後面會總結>,雖然後面程序還有寫bug。他還是很爽快的把錢給付了,說即使程序還有問題也值了,通過這次填坑交流知道要注意很多地方。這算是對我這次工作的最大肯定吧。
總結:
通過對這次填坑和以往DIOCP群裡面一些朋友的問題和做法,我列出下多線程程序編寫需要遵循的幾點,希望對大家有所幫助:
1.子線程千萬不要訪問主線程的UI,(memo,Label),我發現這樣做的程序員很多,在diocp中經常會用到onConnected/OnDisconnected事件中直接操作主窗體的Memo。導致程序無法正常退出,或者出現卡死主界面的情況,原因我想可以歸納到訪問沖突上面,用臨界也不能解決問題。很多組件都是靠windows消息驅動,他才不會使用零件去處理消息,所以臨界也沒辦法。你只有老老實實的投遞到主線程去完成這部分工作,qworker和iocpTask都可以很好的完成這項工作。
2.線程之間訪問共享資源需要用臨界,千萬不要多個線程同時去處理同一個變量,或者列表,否則就等著出現各種問題吧。
3.數據庫連接盡量用連接池去完成,這樣既可以減少連接,也可以很好的避免多個線程對同一個連接的使用。
當然還有很多細小的問題,需要自己去注意了。
程序出現了線程同步的問題。
線程同步是一個系統的,包含內容很廣的問題。多線程編程需要精致地運用線程同步機制以解決資源競爭、互斥、協同及死鎖等多種問題。如果你一定要用多線程實現,那就必須系統地掌握線程同步編程的方法。
建議先學習一本操作系統教材的多線程章節,了解整個知識脈絡(推薦《現代操作系統》,講得很好);再學習一下Windows在多線程方面提供的線程同步對象和同步API;如果你用的是VC,那麼還要學習一下MFC對線程同步API的封裝方法。
當然,您也可以把代碼貼上來讓大家幫忙找出問題所在。
個人認為提高效率完全是扯淡 甚至會拉低整體運算速度 雖然可以在空閒時間中更多利用 但切換線程過程中也會花費時間 盡管十分短暫 甚至感覺不到 intel灌輸的理念。。AMD一直沒有走超線程這條路的原因就是這樣