[問題] Task StartNew過多,導致記憶體爆量

看板C_Sharp作者 (第一天)時間8年前 (2016/07/15 15:11), 8年前編輯推噓2(2027)
留言29則, 6人參與, 最新討論串1/1
===前情提要=== 目前在重整資料庫的資料(約2,800萬筆),所以必須一筆一筆爬 資料爬出來後會做兩種處理,再建立新的資料庫 資料庫使用mongodb,先把整個collection做findall,再丟入foreach的迴圈去跑 用了兩個foreach,為省略版面,以下code只寫一個foreach作為範本 ===方案A,單執行緒=== var result = coll.FindAll(); foreach(var doc in result) { 工作!(); } 結果: 工作A處理效能:20筆/秒 工作B處理效能:10筆/秒 慢慢做記憶體跟CPU都不炸 ===方案B,多執行緒=== var result = coll.FindAll(); foreach(var doc in result) { Task Task_CheckData = Task.Factory.StartNew(() => { 工作!(); }); } 結果: 工作A處理效能:900up筆/秒,持續加速 工作B處理效能:15up筆/秒,緩慢加速,資料庫效能都被工作A吃掉了 爐~心~超~載~啦~ 由於一直生出新的Task,但程式又沒有適當的釋放資源,導致記憶體持續上升 吃完所有實體記憶體後執行速度很緩慢,而且也沒有釋放記憶體的情況 ===方案C,多執行緒+Dispose=== var result = coll.FindAll(); foreach(var doc in result) { Task Task_CheckData = Task.Factory.StartNew(() => { 工作!(); }); Task_CheckData.Wait(); Task_CheckData.Dispose(); } 結果: 工作A處理效能:20筆/秒 工作B處理效能:10筆/秒 體悟心靈祥和ˊㄇˋ 已經變成單執行緒的形狀了 ===方案D,多執行緒+ContinueWith,失敗=== var result = coll.FindAll(); foreach(var doc in result) { Task Task_CheckData = Task.Factory.StartNew(() => { 工作!(); }); Task_CheckData.ContinueWith(antecendent => { Task_CheckData_Each.Dispose(); }, TaskScheduler.FromCurrentSynchronizationContext()); } 結果: 程式整個卡住不跑... D方案比C方案早生出來 因為失敗了所以先前沒key ===問題結論=== 大概出在Task的使用上 但餵狗後還是沒發現比較好的解決方案 或許是我關鍵字下錯QQ 請版上先知指教,謝謝 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 111.82.219.136 ※ 文章網址: https://www.ptt.cc/bbs/C_Sharp/M.1468566664.A.390.html

07/15 15:13, , 1F
Parallel.ForEach?
07/15 15:13, 1F

07/15 15:14, , 2F
順便說一下你方案C不叫多執行緒+dispose,你用了Wait()就是
07/15 15:14, 2F

07/15 15:16, , 3F
把目前的執行緒block到該Task完成後才跑下一個StartNew
07/15 15:16, 3F

07/15 15:16, , 4F
同時間就是只有單執行緒
07/15 15:16, 4F

07/15 15:17, , 5F
難怪會被打回原形,還是得等task完成工作
07/15 15:17, 5F

07/15 15:18, , 6F
感謝提點!請問Parallel.ForEach,建議與Task並用嗎?
07/15 15:18, 6F
補充方案D,感謝各位

07/15 15:34, , 7F
最上面說錯了,Parallel.ForEach跟Task是一樣的...
07/15 15:34, 7F

07/15 15:35, , 8F
應該要先看方案B問題是出在哪,應該不是Task沒有釋放資源
07/15 15:35, 8F

07/15 15:37, , 9F
或許是新增Task的速度大於Task執行完成的速度?
07/15 15:37, 9F

07/15 15:37, , 10F
但是task不像threadpool有數量的限制...
07/15 15:37, 10F

07/15 15:38, , 11F
如果是新增速度大於完成速度,可能要調整已經在等待執行的
07/15 15:38, 11F

07/15 15:38, , 12F
Task數量太多,就先暫停新增的動作
07/15 15:38, 12F

07/15 15:38, , 13F
Task底層應該還是用ThreadPool做,所以應該不是Thread太多
07/15 15:38, 13F

07/15 15:41, , 14F
是排在queue上的Task太多
07/15 15:41, 14F
將迴圈改為Parallel.ForEach 工作A處理效能:500up筆/秒,緩慢增加 工作B處理效能:230up筆/秒,緩慢增加 記憶體有增長,但是成長的量不大,且有明顯回收記憶體的現象,持續觀察~ 非常感謝!

07/15 15:53, , 15F
開幾個task持續處理資料不要新開就行了吧~ 你這方式
07/15 15:53, 15F

07/15 15:53, , 16F
感覺額外負擔太大了...@@
07/15 15:53, 16F
的確是..之後會試著把Task在迴圈外宣告 然後迴圈內加入新工作 感恩!

07/15 15:54, , 17F
task 無限增生? 就算你程式夠強吃得下,你還是會卡在IO
07/15 15:54, 17F

07/15 15:55, , 18F
導致整個程序卡住。最好的作法是把資料分成chunk,每個
07/15 15:55, 18F

07/15 15:56, , 19F
chunk 限制筆數,把chunk丟到 thread 去跑,再視情況調
07/15 15:56, 19F

07/15 15:57, , 20F
整thread 的數量
07/15 15:57, 20F

07/15 15:59, , 21F
但就算方案A ,一秒只有20筆,是不是有什麼誤會..
07/15 15:59, 21F
切chunk的方式我有想過,但是資料庫資料蠻不連續的 可能需要用第A筆至第B筆來切 速度的部分,目前是連線去呼叫資料庫,查詢時間略久,每做一次查詢回傳約1xx毫秒 一個工作內會做1~4次查詢 感謝你的建議與問題~~ ※ 編輯: moumou17 (111.82.219.136), 07/15/2016 16:06:00

07/15 16:35, , 22F
你的bound在資料庫(I/O bound),用多執行續不太會增加效率
07/15 16:35, 22F

07/15 16:36, , 23F
一些情況反而會更差,就好像你同時開好幾個執行續去讀同一
07/15 16:36, 23F

07/15 16:36, , 24F
顆硬碟的資料一樣
07/15 16:36, 24F

07/15 16:40, , 25F
這種情況要做多執行續優化,不應該用件數(平行)去切割,而
07/15 16:40, 25F

07/15 16:40, , 26F
是要依工作類型(垂直)去切。
07/15 16:40, 26F

07/15 17:12, , 27F
同意樓上,如果你一個工作內容要存取4次DB,應該有其他
07/15 17:12, 27F

07/15 17:13, , 28F
方式可以減少DB存取的次數,有機會提高更多效率
07/15 17:13, 28F

07/16 09:46, , 29F
Parallel.For 速度超快的 越多核心越快
07/16 09:46, 29F
文章代碼(AID): #1NY8o8EG (C_Sharp)