本書由Python pandas項目創始人Wes McKinney親筆撰寫,詳細介紹利用Python進行操作、處理、清洗和規整數據等方面的具體細節和基本要點。第2版針對Python 3.6進行全面修訂和更新,涵蓋新版的pandas、NumPy、IPython和Jupyter,並增加大量實際案例,可以 ...
mormot.core.threads--TSynQueue
以下是對 mormot.core.threads
中部分代碼的翻譯,特別是關於 TSynQueue
類的部分:
const
// 在這裡定義以避免在uses子句中顯式鏈接到syncobjs單元
wrSignaled = syncobjs.wrSignaled; // 等待結果:已發出信號
wrTimeout = syncobjs.wrTimeout; // 等待結果:超時
wrError = syncobjs.wrError; // 等待結果:錯誤
type
// 在這裡定義以避免在uses子句中顯式鏈接到syncobjs單元
// - 請註意,您可能更想使用來自mormot.core.os.pas的TSynEvent
TWaitResult = syncobjs.TWaitResult; // 等待操作的結果類型
// 在這裡定義以避免在uses子句中顯式鏈接到syncobjs單元
// - 請註意,您可能更想使用來自mormot.core.os.pas的TSynEvent
TEvent = syncobjs.TEvent; // 事件對象類型
{$endif PUREMORMOT2}
type
// TThread的動態數組類型
TThreadDynArray = array of TThread;
// 由本單元引發的異常類
ESynThread = class(ESynException);
{ ************ 線程安全的TSynQueue和TPendingTaskList }
type
// 線程安全的FIFO(先進先出)記錄隊列
// - 內部使用TDynArray存儲,採用滑動演算法,比FPC或Delphi的TQueue或簡單的TDynArray.Add/Delete更高效
// - 如果需要,支持TSynPersistentStore二進位持久化
// - 此結構也是線程安全的
TSynQueue = class(TSynPersistentStore)
protected
// ...(省略了保護成員的詳細翻譯,它們主要是內部實現細節)
public
/// 初始化隊列存儲
// - aTypeInfo應該是存儲在此TSynQueue實例中的值的動態數組TypeInfo() RTTI指針
// - 可以選擇性地為此實例分配一個名稱
constructor Create(aTypeInfo: PRttiInfo; const aName: RawUtf8 = ''); reintroduce; virtual;
/// 釋放存儲
// - 將釋放所有內部存儲的值,並調用WaitPopFinalize
destructor Destroy; override;
/// 將一個項目推入隊列
// - 此方法是線程安全的,因為它會鎖定實例
procedure Push(const aValue);
/// 從隊列中提取一個項目,作為FIFO(先進先出)
// - 如果aValue已被填充為掛起的項目,則返回true,並且該項目從隊列中移除(如果不想移除,請使用Peek)
// - 如果隊列為空,則返回false
// - 此方法是線程安全的,因為它會鎖定實例
function Pop(out aValue): boolean;
/// 從隊列中提取一個匹配的項目,作為FIFO(先進先出)
// - 將當前掛起的項目與aAnother值進行比較
function PopEquals(aAnother: pointer; aCompare: TDynArraySortCompare;
out aValue): boolean;
/// 從隊列中查找一個項目,作為FIFO(先進先出)
// - 如果aValue已被填充為掛起的項目,則返回true,並且該項目不會從隊列中移除(與Pop方法不同)
// - 如果隊列為空,則返回false
// - 此方法是線程安全的,因為它會鎖定實例
function Peek(out aValue): boolean;
/// 等待並從隊列中提取一個項目,作為FIFO(先進先出)
// - 如果在指定的aTimeoutMS時間內aValue已被填充為掛起的項目,則返回true
// - 如果沒有在時間內將項目推入隊列,或者已調用WaitPopFinalize,則返回false
// - aWhenIdle可用於空閑時處理消息,例如VCL/LCL的Application.ProcessMessages
// - 您可以選擇在返回之前比較掛起的項目(當多個線程將項目放入隊列時可能很有用)
// - 此方法是線程安全的,但僅在需要時鎖定實例
function WaitPop(aTimeoutMS: integer; const aWhenIdle: TThreadMethod;
out aValue; aCompared: pointer = nil;
aCompare: TDynArraySortCompare = nil): boolean;
/// 在隊列中等待查找一個項目,作為FIFO(先進先出)
// - 在aTimeoutMS時間內返回一個指向掛起項目的指針
// - 保持Safe.ReadWriteLock,因此調用者可以檢查其內容,然後如果它是預期的,則調用Pop(),並最終調用Safe.ReadWriteUnlock
// - 如果沒有在時間內將項目推入隊列,則返回nil
// - 此方法是線程安全的,但僅在需要時鎖定實例
function WaitPeekLocked(aTimeoutMS: integer;
const aWhenIdle: TThreadMethod): pointer;
/// 確保任何掛起或未來的WaitPop()立即返回false
// - 總是由Destroy析構函數調用
// - 也可以從例如UI的OnClose事件中調用,以避免任何鎖定
// - 此方法是線程安全的,但僅在需要時鎖定實例
procedure WaitPopFinalize(aTimeoutMS: integer = 100);
/// 刪除此隊列中當前存儲的所有項目,並清空其容量
// - 此方法是線程安全的,因為它會鎖定實例
procedure Clear;
/// 用存儲的隊列項目初始化一個動態數組
// - aDynArrayValues應該是Create方法中aTypeInfo定義的變數
// - 您可以檢索一個可選的TDynArray包裝器,例如用於二進位或JSON持久化
// - 此方法是線程安全的,並將複製隊列數據
procedure Save(out aDynArrayValues; aDynArray: PDynArray = nil); overload;
/// 返回當前存儲在此隊列中的項目數
// - 此方法不是線程安全的,因此返回的值應是指示性的,或者您應使用顯式的Safe鎖/解鎖
// - 如果您想檢查隊列是否為空,請調用Pending
function Count: integer;
/// 返回當前在記憶體中保留的槽位數
// - 隊列具有優化的自動調整大小演算法,您可以使用此方法返回其當前容量
// - 此方法不是線程安全的,因此返回的值是指示性的
function Capacity: integer;
/// 如果隊列中有一些項目當前掛起,則返回true
// - 比檢查Count=0更快,並且比Pop或Peek快得多
// - 此方法不是線程安全的,因此返回的值是指示性的
function Pending: boolean;
{$ifdef HASINLINE}inline;{$endif}
end;
這個翻譯提供了對 TSynQueue
類及其成員、方法和屬性的概述,以便更好地理解其設計目的和使用方式。請註意,翻譯過程中省略了保護成員的詳細翻譯,因為它們主要是內部實現細節,對於外部使用來說不是必需的。
在Free Pascal環境下,使用 TSynQueue
類的一個示例會涉及創建隊列實例、向隊列中添加元素、從隊列中提取元素,以及處理可能的併發訪問。由於 TSynQueue
是線程安全的,因此它非常適合在多線程應用程式中使用。然而,為了簡化示例,我們將在一個單線程環境中展示其基本用法。
請註意,由於 TSynQueue
可能是特定於某個庫(如mORMot)的,因此您可能需要確保該庫已被正確安裝並包含在您的項目中。以下是一個簡化的使用示例:
program TSynQueueExample;
{$MODE DELPHI}
{$APPTYPE CONSOLE}
uses
SysUtils, // 包含WriteLn等標準輸出函數
mormot.core.threads;
type
// 定義一個簡單的記錄類型,用於存儲在TSynQueue中
TMyData = record
ID: Integer;
Value: String;
end;
var
Queue: TSynQueue;
Data: TMyData;
begin
try
// 創建TSynQueue實例,傳遞TMyData類型的TypeInfo
Queue := TSynQueue.Create(TypeInfo(TMyDataArray), 'MyDataQueue');
try
// 向隊列中添加數據
Queue.Push(TMyData.Create(1, 'First'));
Queue.Push(TMyData.Create(2, 'Second'));
Queue.Push(TMyData.Create(3, 'Third'));
// 註意:上面的Push調用實際上是有問題的,因為TMyData是一個記錄類型,
// 它不是通過Create方法創建的。這裡只是為了演示如何調用Push。
// 在實際使用中,您應該直接傳遞記錄的值,如下所示:
// Queue.Push((ID: 1; Value: 'First')); // 但這取決於TSynQueue的實現是否支持記錄值傳遞
// 由於記錄類型通常是通過值傳遞的,並且TSynQueue可能設計為存儲記錄的副本,
// 因此您應該這樣做:
Queue.Push((ID: 1, Value: 'First'));
Queue.Push((ID: 2, Value: 'Second'));
Queue.Push((ID: 3, Value: 'Third'));
// 從隊列中提取數據(FIFO)
while Queue.Pop(Data) do
begin
WriteLn('Popped Data: ID = ', Data.ID, ', Value = ', Data.Value);
end;
// 此時隊列應為空
if not Queue.Pending then
WriteLn('Queue is empty.');
finally
// 銷毀TSynQueue實例
Queue.Free;
end;
except
on E: Exception do
WriteLn('Error: ', E.Message);
end;
WriteLn('Program ended.');
end.
重要註意事項:
- 在上面的示例中,我使用了
TMyDataArray
作為TypeInfo
的參數,但實際上TypeInfo(TMyDataArray)
可能不是有效的,因為TMyDataArray
在示例中並未定義。通常,您應該傳遞記錄類型本身的TypeInfo
,但TSynQueue
可能期望一個動態數組類型來存儲其元素。然而,由於TSynQueue
的設計允許它存儲記錄的副本(而不是指針),因此您可能不需要定義一個動態數組類型。在實際使用中,您應該查閱TSynQueue
的文檔以確定如何正確地傳遞TypeInfo
。 - 記錄類型通常是通過值傳遞的,並且上面的
Push
調用示例假設TSynQueue
能夠處理記錄值的直接傳遞。這取決於TSynQueue
的具體實現。如果TSynQueue
被設計為存儲指向記錄的指針,那麼您可能需要定義一個動態數組類型或使用其他機制來傳遞記錄。 - 由於
TSynQueue
是線程安全的,因此在多線程環境中使用時,您不需要擔心併發訪問問題。但是,在上面的示例中,我們為了簡化而在一個單線程環境中展示了其基本用法。 - 請確保將
'YourSynapseUnit'
替換為實際包含TSynQueue
定義的單元名稱。如果TSynQueue
是mORMot庫的一部分,那麼您可能需要包含mORMot的相應單元。
以下是對 TPendingTaskList
及其相關類型的翻譯,包括其保護類型、構造函數、方法和屬性:
type
/// 內部項定義,用於TPendingTaskList存儲
// 該記錄定義了待執行任務的時間戳和任務內容(以RawByteString形式存儲)
TPendingTaskListItem = packed record
/// 當TPendingTaskList.GetTimestamp達到此值時,應執行該任務
Timestamp: Int64;
/// 與此時間戳相關聯的任務,以原始二進位字元串形式存儲
Task: RawByteString;
end;
/// 內部列表定義,用於TPendingTaskList存儲
// TPendingTaskListItem的動態數組
TPendingTaskListItemDynArray = array of TPendingTaskListItem;
/// 線程安全的任務列表,任務以RawByteString形式存儲,並帶有時間戳
// - 您可以向內部列表添加任務,在給定延遲後執行,使用類似發佈/查看的演算法
// - 執行延遲可能不准確,但會根據每次調用NextPendingTask和GetTimestamp的解析度進行最佳猜測
TPendingTaskList = class
protected
// 內部存儲結構和同步訪問
fTask: TPendingTaskListItemDynArray; // 存儲待執行任務的數組
fTasks: TDynArrayLocked; // 對fTask數組的封裝,提供線程安全的訪問
// 獲取當前存儲的任務數量(線程安全)
function GetCount: integer;
// 獲取當前時間戳(預設為GetTickCount64)
function GetTimestamp: Int64; virtual;
public
// 初始化列表的記憶體和資源
constructor Create; reintroduce;
// 添加一個任務,指定從當前時間開始的延遲(毫秒)
procedure AddTask(aMilliSecondsDelayFromNow: integer;
const aTask: RawByteString); virtual;
// 添加多個任務,指定任務之間的延遲(毫秒)
// - 第一個提供的延遲將從當前時間開始計算,然後指定下一個提供的任務之間的等待時間
// - 也就是說,aMilliSecondsDelays不是絕對延遲
procedure AddTasks(const aMilliSecondsDelays: array of integer;
const aTasks: array of RawByteString);
// 檢索下一個待執行的任務
// - 如果沒有在當前時間可用的計劃任務,則返回''
// - 根據指定的延遲返回下一個任務
function NextPendingTask: RawByteString; virtual;
// 清空所有待執行的任務
procedure Clear; virtual;
// 訪問內部存儲的TPendingTaskListItem.Timestamp值
// - 對應當前時間
// - 預設實現返回GetTickCount64,在Windows下典型解析度為16毫秒
property Timestamp: Int64 read GetTimestamp;
// 當前定義了多少個待執行任務
property Count: integer read GetCount;
// 對內部任務列表的直接低級訪問
// - 警告:此動態數組的長度是列表的容量:請使用Count屬性來檢索存儲的項的確切數量
// - 使用Safe.Lock/TryLock與try ... finally Safe.Unlock塊進行線程安全的訪問
// - 項按時間戳遞增存儲,即第一項是NextPendingTask方法將返回的下一個項
property Task: TPendingTaskListItemDynArray read fTask;
end;
TPendingTaskList
類提供了一種機制來存儲和按計劃執行一系列任務,每個任務都與一個時間戳相關聯。通過調用 AddTask
或 AddTasks
方法,您可以將任務添加到列表中,這些任務將在指定的延遲後執行。NextPendingTask
方法用於檢索下一個待執行的任務,而 Clear
方法用於清空整個任務列表。
註意,TPendingTaskList
類中的 Timestamp
屬性和 GetTimestamp
方法是用於確定何時執行任務的關鍵。GetTimestamp
方法預設返回 GetTickCount64
的值,但在子類中可以根據需要進行重寫,以提供不同的時間戳生成邏輯。同樣,NextPendingTask
方法也是虛擬的,允許在子類中實現自定義的任務檢索邏輯。
在Free Pascal環境下,結合 TPendingTaskList
類的定義,我們可以編寫一個示常式序來展示這兩個類的基本用法。以下是一個簡化的示例,一個 TPendingTaskList
實例來按計劃執行任務(在這個例子中,任務只是簡單地列印消息)。
請註意,由於 TPendingTaskList
可能是特定於某個庫(如mORMot)的,因此您需要確保該庫已被正確安裝並包含在您的項目中。此外,為了簡化示例,我們將在一個單線程環境中運行它,儘管這些類設計用於多線程環境。
program PendingTaskListExample;
{$MODE DELPHI}
{$APPTYPE CONSOLE}
uses
SysUtils, // 包含WriteLn等標準輸出函數
YourSynapseUnit; // 替換為實際包含這些類定義的單元名稱
var
Queue: TSynQueue;
TaskList: TPendingTaskList;
I: Integer;
TaskMessage: RawByteString;
begin
try
// 創建TPendingTaskList實例來按計劃執行任務
TaskList := TPendingTaskList.Create;
try
// 添加一些計劃任務到列表中
// 假設每個任務只是列印一條消息,延遲從當前時間開始計算
TaskList.AddTask(1000, 'Task 1 in 1 second'); // 1秒後執行
TaskList.AddTask(2000, 'Task 2 in 2 seconds'); // 2秒後執行
// 註意:由於這個示例是在單線程環境中運行的,
// 我們不會等待任務實際執行。在實際應用中,
// 您可能需要在另一個線程中調用NextPendingTask,
// 或者使用某種形式的定時器或事件迴圈來檢查並執行任務。
// 為了模擬任務執行,我們可以手動調用NextPendingTask
// 並列印消息(但在實際應用中,這通常不是您想要的方式)
repeat
TaskMessage := TaskList.NextPendingTask;
if TaskMessage <> '' then
WriteLn('Executing Task: ', TaskMessage)
else
Break; // 沒有更多待執行的任務,退出迴圈
// 在這裡,我們實際上應該等待一段時間再檢查下一個任務,
// 但為了簡化示例,我們只是立即再次檢查(這不是實際用法)
until False;
finally
// 銷毀TPendingTaskList實例(在這個簡單的示例中可能不是必需的,
// 但為了完整性而包含)
TaskList.Free;
end;
except
on E: Exception do
WriteLn('Error: ', E.Message);
end;
WriteLn('Program ended.');
end.
重要註意事項:
- 單線程執行:上面的示例是在單線程環境中運行的,因此它不會按預期等待任務實際執行。在實際應用中,您應該在一個單獨的線程中或在事件迴圈中定期調用
NextPendingTask
來檢查並執行任務。 - 模擬任務執行:為了簡化示例,我們手動調用了
NextPendingTask
並立即列印了消息。在實際應用中,您應該根據NextPendingTask
的返回值來決定是否執行任務,並且您可能需要等待一段時間再檢查下一個任務。 - 替換單元名稱:請確保將
'YourSynapseUnit'
替換為實際包含TSynQueue
和TPendingTaskList
類定義的單元名稱。 - 錯誤處理:示例中包含了基本的錯誤處理邏輯,但在實際應用中,您可能需要更詳細的錯誤處理和日誌記錄。
- 線程安全:儘管
TSynQueue
和TPendingTaskList
是線程安全的,但在從多個線程訪問它們時,您仍然需要確保正確地同步對它們的訪問(儘管在這個簡單的示例中我們沒有這樣做)。在實際應用中,您可能需要使用鎖、信號量或其他同步機制來確保線程安全。然而,在這個特定的示例中,由於我們是在單線程環境中運行,因此不需要擔心線程安全問題。