Windows 與 C++

執行緒集區同步處理

Kenny Kerr

 

我已說過: 封鎖的作業是雙面夾攻來進行同步。通常,不過,您必須等候某種資源變成可用,或也許您正在實作規定一段時間才會重新傳送網路封包所需的通訊協定。該怎麼辦然後?您可以使用重要區段、 呼叫函式,例如睡眠和 WaitForSingleObject,以此類推。當然,這表示您將有執行緒無所事事封鎖一次。您需要的是在執行緒集區,等待您之第三人,而不會影響其並行處理限制,我在 9 月 2011年專欄中討論過的方法 (msdn.microsoft.com/magazine/hh394144)。執行緒集區有可用資源,或這段時間之後,可以再佇列回呼。

在本月的專欄中,我將告訴您如何您可以輕鬆辦到。隨著我在設定 2011 年 8 月專欄中介紹的工作物件 (msdn.microsoft.com/magazine/hh335066),執行緒集區 API 提供了數個其他回呼產生的物件。這個月,我將告訴您如何使用等候物件。

等候物件

執行緒集區的等候物件用於同步處理。而不是重要的區段區塊 — 或 slim 讀取器/寫入器鎖定,您可以等候核心同步物件,通常事件或號誌,變成已收到訊號。

雖然您可以使用 WaitForSingleObject 與朋友、 等候物件整合得很好的執行緒集區 API 的其餘部分。它會非常有效率地將送出之後,減少所需的執行緒數目與您要撰寫及偵錯的程式碼數量任何等候物件群組在一起。這可讓您使用執行緒集區環境,並清除群組,也讓您免於必須以專用的一或多個執行緒,等候物件變成已收到訊號。在執行緒集區的核心部分的改良功能,受限於它能在某些情況下甚至達此目的 threadless 的方式。

CreateThreadpoolWait 函式會建立等候物件。如果函式成功,則會傳回不透明的指標,表示等候物件。如果失敗,它會傳回 null 指標值的連線,並提供透過時發生函式的詳細資訊。指定工作的物件,CloseThreadpoolWait 函數通知執行緒集區物件可能會被釋放。這個函式沒有傳回值,並等候物件的效率的假設有效。

我在 7 月 2011年專欄中介紹的 unique_handle 類別樣板 (msdn.microsoft.com/magazine/hh288076) 就會自己料理這些詳細資料。

以下是可以使用 unique_handle,以及 typedef 十分方便特性類別:

struct wait_traits
{
  static PTP_WAIT invalid() throw()
  {
    return nullptr;
  }
 
  static void close(PTP_WAIT value) throw()
  {
    CloseThreadpoolWait(value);
  }
};
 
typedef unique_handle<PTP_WAIT, wait_traits> wait;

我現在可以使用 typedef,並建立等候物件,如下所示:

void * context = ...
wait w(CreateThreadpoolWait(callback, context, nullptr));
check_bool(w);

像往常一樣,最後一個參數並選擇性地接受對環境的指標,讓您可以等候物件關聯的環境中,如我在 9 月專欄中所述。 第一個參數是會排入執行緒集區中,等候完成後的回呼函式。 等候回撥宣告,如下所示:

void CALLBACK callback(PTP_CALLBACK_INSTANCE, void * context, PTP_WAIT, TP_WAIT_RESULT);

回撥 TP_WAIT_RESULT 引數就是為什麼在等候完成的原因使用不帶正負號的整數。 WAIT_OBJECT_0 表示已滿足等候,因為同步物件變成信號。 此外,WAIT_TIMEOUT 表示逾時間隔經過同步處理物件發出訊號之前。 如何指出等候逾時和同步處理的物件? 這是相當複雜的 SetThreadpoolWait 函式的工作。 您試著指定的逾時,這項功能十分簡單。 請考量以下範例:

handle e(CreateEvent( ...
));
check_bool(e);
 
SetThreadpoolWait(w.get(), e.get(), nullptr);

首先,我建立事件物件,從我年 7 月的專欄中使用 unique_handle typedef。 顯而易見地,SetThreadpoolWait 函式會設定等候物件是等待同步物件。 最後一個參數表示選擇性的逾時,但在這個範例中,我提供 null 指標值,指出執行緒集區應該永遠等待。

FILETIME 結構

但特定的逾時呢? 這就是,此時事情就比較難以處理。 例如,WaitForSingleObject 的函式可讓您以毫秒為單位的等候逾時值設定為不帶正負號的整數。 SetThreadpoolWait 函式,不過,必須是變數的指標,FILETIME 結構中,開發人員會看到一些挑戰。 FILETIME 結構是 64 位元值,表示絕對日期和時間自 1601 年的開始,以 100 奈秒間隔 (根據 「 時間 」)。

若要調整相對的時間間隔,SetThreadpoolWait 會將 FILETIME 結構視為帶正負號的 64 位元值。 如果提供負值,則它接受不帶正負號的值做為時間間隔,相對於目前的時間,一次以 100 個十億分之一秒的間隔。 值得一提的是相對的計時器會停止計算當電腦進入休眠狀態或進入休眠。 絕對的逾時值是很明顯地不受影響。 總之,這種 FILETIME 不方便的其中一個的絕對或相對的逾時值。

絕對的逾時最簡單的方法可能是填寫了 SYSTEMTIME 結構,然後使用 SystemTimeToFileTime 函數來準備您的 FILETIME 結構:

SYSTEMTIME st = {};
st.wYear = ...
st.wMonth = ...
st.wDay = ...
st.wHour = ...
// etc.
FILETIME ft;
check_bool(SystemTimeToFileTime(&st, &ft));
 
SetThreadpoolWait(w.get(), e.get(), &ft);

相對的逾時值,如涉及進一步發揮創意。 首先,您必須將一些相對的時間轉換成 100 個十億分之一秒的間隔,然後再將其轉換為負值的 64 位元。 後者是想像它看起來的。 請記住電腦會代表負數的值必須設高成最大顯著性位元的效果與使用兩個補數系統,帶正負號的整數。 新增到這是 FILETIME 實際上包含兩個 32 位元值。 這種方法,您也需要時將它視為 64 位元值正確處理電腦的對齊方式,否則對齊錯誤可能會發生。 此外,您不能直接使用較低的 32 位元值來儲存值,較高的 32 位元值的最大顯著性的位元現狀。

相對的等待時間截止值轉換

通常會表達相對的逾時,以毫秒為單位,因此我示範這裡該轉換。 回想一下,以毫秒為第二個 922337203685 奈秒是 billionth 的秒數部分。 若要查看它的另一種方式是以毫秒 1000 微秒而且微秒是 1000 奈秒。 以毫秒就 10000 的 100 奈秒為單位,SetThreadpoolWait 所需的度量單位。 有許多方法來實現此安全性,不過還有的運作方式的其中一種方法:

DWORD milliseconds = ...
auto ft64 = -static_cast<INT64>(milliseconds) * 10000;
 
FILETIME ft;
memcpy(&ft, &ft64, sizeof(INT64));
 
SetThreadpoolWait(w.get(), e.get(), &ft);

請注意我仔細地轉型之前乘法 DWORD,以防止整數溢位。 我也使用 memcpy,因為 reinterpret_cast 需要 FILETIME 的 8 位元組界限對齊。 你,不用多說,辦,相反地,但這是有點更簡潔。 甚至更簡單的方法是利用 Visual c + + 編譯器對齊的聯集最大對齊需求,其成員的任何事實。 事實上,如果正確順序的聯集的成員,你就可以在單一行中,如下:

union FILETIME64
{
  INT64 quad;
  FILETIME ft;
};
 
FILETIME64 ft = { -static_cast<INT64>(milliseconds) * 10000 };
 
SetThreadpoolWait(w.get(), e.get(), &ft.ft);

足夠的編譯器技巧。 讓我們回到執行緒集區。 您可能會想要再試的另一項是零的逾時。 這通常是用來判斷是否同步物件收到信號不實際封鎖並等待的情況下使用 WaitForSingleObject。 但是,此程序不支援由執行緒集區中,所以最好還是離不開 WaitForSingleObject。

如果您想要停止等候其同步處理物件的特定工作物件,然後只要呼叫 SetThreadpoolWait 時發現 null 指標值做為第二個參數。 只要小心明顯的競爭。

最後要等候的物件相關的函式是 WaitForThreadpoolWaitCallbacks。 首先,它可能會出現類似於使用我在 8 月專欄中介紹的工作物件的 WaitForThreadpoolWorkCallbacks 函式。 不要讓它在欺騙您。 WaitForThreadpoolWaitCallbacks 函數會解譯為常值沒有其名所示。 它會等待從特定的等候物件的任何回呼。

比較麻煩的是等候物件會僅佇列回呼相關聯的同步物件收到信號,或逾時。 發生這些事件其中一項,直到沒有回呼會排入佇列,並沒有任何等候函式,要等候的項目。 解決方法是先呼叫 SetThreadpoolWait 時發現 null 指標值,告知停止插撥功能,並接著會呼叫 WaitForThreadpoolWaitCallbacks,以避免任何競爭情形的等候物件:

SetThreadpoolWait(w.get(), nullptr, nullptr);
WaitForThreadpoolWaitCallbacks(w.get(), TRUE);

跟您預期的一樣,第二個參數會決定是否任何擱置中回呼可能透過具有進度落後,但還沒有開始執行將會取消與否。 當然,等待清除群組與物件工作。 您可以閱讀我年 10 月 2011年 (msdn.microsoft.com/magazine/hh456398) 資料行,以了解如何使用清除群組。 在較大型的專案,它們真的有助於簡化很多需要技巧的取消和需要完成的清除。

 

Kenny Kerr 是軟體工匠,與經驗,熱衷於原生的 Windows 開發。到達他在 kennykerr.ca

因為有到下列的技術專家來檢閱這份文件: Pedro Teixeira