我有一個程序,做一個有限的多線程形式。它是用Delphi編寫的,並使用libmysql.dll(C API)訪問MySQL服務器。該程序必須處理一長串記錄,每記錄約0.1秒。把它看作一個大循環。所有的數據庫訪問都由工作線程完成,這些線程可以預取下一個記錄或寫入結果,因此主線程不必等待。如何才能使SQL查詢線程啓動,然後在獲取結果之前做其他工作?
在這個循環的頂部,我們首先等待預取線程,獲取結果,然後讓預取線程爲下一條記錄執行查詢。這個想法是預取線程將立即發送查詢,並在主線程完成循環時等待結果。
它經常這樣工作。但是請注意,沒有什麼可以確保預取線程立即運行。我發現通常這個查詢並沒有被髮送,直到主線程循環並開始等待預取。
我通過在啓動預取線程後立即調用sleep(0)來解決這個問題。通過這種方式,主線程放棄了剩餘的時間片,希望預取線程現在可以運行,併發送查詢。然後即線程在等待時會睡眠,這就允許主線程再次運行。
當然,在操作系統中運行的線程還有很多,但這在一定程度上確實有效。
我真正想要發生的是主線程發送查詢,然後讓工作線程等待結果。我使用libmysql.dll在工作線程中調用
result := mysql_query(p.SqlCon,pChar(p.query));
。相反,我想讓主線程調用類似於
mysql_threadedquery(p.SqlCon,pChar(p.query),thread);
這會在數據發佈後立即切換任務。
有人知道這樣的事嗎?
這實際上是一個調度問題,所以我可以嘗試以更高的優先級開啓預取線程,然後在發送查詢後降低它的優先級。但是,再次,我沒有任何mysql調用分開發送查詢和接收結果。
也許它在那裏,我只是不知道它。請賜教。
問題補充:
有誰覺得這個問題會通過更高的優先級比主線程中運行的預取線程來解決?這個想法是,預取將立即搶佔主線程併發送查詢。然後它會睡覺等待服務器回覆。同時主線程將運行。
新增:當前實現
這個程序執行包含在一個MySQL數據庫的數據進行計算的詳細信息。有33M項目每秒增加更多。程序不斷運行,處理新項目,有時重新分析舊項目。它從表格中獲得要分析的項目列表,因此在通過(當前項目)的開始處,它知道它將需要的下一個項目ID。
由於每個項目都是獨立的,因此這是多處理的理想目標。最簡單的方法是在多臺機器上運行該程序的多個實例。該程序通過分析,重寫和算法重新設計高度優化。當數據不足時,單個實例仍然使用100%的CPU內核。我在兩個四核工作站上運行4-8份拷貝。但以這樣的速度,他們必須花時間在MySQL服務器上等待。 (優化服務器/數據庫模式是另一個主題。)
我在此過程中實現了多線程,目的只是爲了避免阻塞SQL調用。這就是我稱之爲「有限多線程」的原因。工作者線程有一項任務:發送命令並等待結果。 (OK,兩項任務。)
原來有6個阻塞任務與6個表相關聯。其中兩個讀取數據和另外4個寫入結果。這些足夠相似,可以由一個通用的任務結構來定義。一個指向這個Task的指針被傳遞給一個線程池管理器,它分配一個線程來完成這項工作。主線程可以通過任務結構檢查任務狀態。
這使得主線程代碼非常簡單。當它需要執行Task1時,它會等待Task1不忙,將SQL命令放入Task1並將其傳遞。當Task1不再忙時,它包含結果(如果有的話)。
寫入結果的4個任務是微不足道的。主線程有一個任務寫入記錄,當它進入下一個項目時。完成該項目後,確保先前的寫入在完成之前完成。
2個閱讀線程並不重要。將讀取傳遞給一個線程,然後等待結果,將不會獲得任何結果。相反,這些任務預取下一個項目的數據。所以來到這個阻塞任務的主線程檢查預取是否完成;如果需要,等待預取完成,然後從任務中獲取數據。最後,它用NEXT Item ID重新發布任務。
這個想法是爲預取任務立即發出查詢並等待MySQL服務器。然後主線程可以處理當前項目,並且在下一個項目開始時,它需要的數據位於預取任務中。
所以線程,線程池,同步,數據結構等都完成了。所有的作品。我剩下的是一個調度問題。
調度問題是這樣的:所有的速度增益都在處理當前項目,而服務器正在獲取下一個項目。我們在處理當前項目之前發出預取任務,但我們如何保證它開始?操作系統調度程序不知道預取任務對於立即發出查詢很重要,然後它只會等待。
操作系統調度程序試圖「公平」並允許每個任務運行指定的時間片。我最糟糕的情況是這樣的:主線程收到它的片併發出預取,然後完成當前項目並且必須等待下一個項目。等待釋放剩下的時間片,所以調度器啓動預取線程,該線程發出查詢然後等待。現在兩個線程都在等待。當服務器發出查詢完成信號時,預取線程重新啓動,並請求結果(數據集)然後休眠。當服務器提供預取線程喚醒的結果時,標記任務完成並終止。最後,主線程重新啓動並從完成的任務中獲取數據。
爲了避免這種最壞情況的調度,我需要一些方法來確保預取查詢是在主線程繼續使用當前項目之前發出的。到目前爲止,我認爲有三種方式來做到這一點:在發出預取任務後,右鍵
,主線程調用Sleep(0)。這應該放棄其餘的時間片。我然後希望調度程序運行預取線程,它將發出查詢,然後等待。然後調度程序應該重新啓動主線程(我希望)。儘管它聽起來很糟糕,但實際上它比沒有更好。
我可能會發出預取線程比主線程更高的優先級。這應該會導致調度程序立即運行它,即使它必須搶佔主線程。它也可能有不良影響。後臺工作者線程獲得更高的優先級似乎是不自然的。
我可能異步發出查詢。也就是說,單獨從接收結果發送查詢。這樣我可以讓主線程使用mysql_send_query(非阻塞)發送預取並繼續使用當前項目。然後,當它需要下一個項目時,它會調用mysql_read_query,這會阻塞,直到數據可用。
請注意,解決方案3甚至不使用工作線程。這看起來像是最好的答案,但需要重寫一些底層代碼。我目前正在尋找這種異步客戶端 - 服務器訪問的例子。
我也想就這些方法提供任何有經驗的意見。我錯過了什麼,或者我做錯了什麼?請注意,這是所有工作代碼。我不是在問怎麼做,而是如何做得更好/更快。
縱觀mysql.pas包裝,我發現了兩個函數mysql_send_query和mysql_read_query,聽起來像我需要的東西。谷歌然後讓我去http://jan.kneschke.de/2008/9/9/async-mysql-queries-with-c-api/誰寫道:「...是公開的,但沒有記錄。不阻止我們。「這看起來很有希望,但我仍然可以使用關於如何正確操作的建議。 – 2010-10-20 05:56:25
通常你有一個處理查詢的線程,不是發送查詢,而是另一個查詢結果。 – 2010-10-20 08:01:45
是的,我現在就這樣做。我有一個預取線程發送查詢來選擇下一條記錄。理論上,我應該在線程等待結果時處理當前記錄。在實踐中,不能保證預取線程馬上啓動。 – 2010-10-21 01:31:19