HOW TO:使用過度訂閱使延遲產生位移
過度訂閱可以針對某些包含延遲性較高之工作的應用程式改善其整體效率。本主題說明如何使用過度訂閱,緩解從網路連接讀取資料時所產生的延遲。
範例
這個範例會使用非同步代理程式程式庫,從 HTTP 伺服器下載檔案。http_reader類別衍生自 concurrency::agent 及使用訊息傳遞,以非同步方式讀取下載那些 URL 名稱。
http_reader類別使用 concurrency::task_group 類別來同時讀取每個檔案。每個工作都會呼叫 concurrency::Context::Oversubscribe 方法以_BeginOversubscription參數設為true啟用目前內容中的過度訂閱。然後,每項工作都會使用 Microsoft Foundation Class (MFC) CInternetSession 和 CHttpFile 類別來下載檔案。最後,每項工作都會呼叫 Context::Oversubscribe 並將 _BeginOversubscription 參數設定為 false,以便停用過度訂閱。
啟用過度訂閱時,執行階段會建立一個用來執行工作的額外執行緒。其中每個執行緒也可以過度訂閱目前的內容,因而建立額外的執行緒。http_reader類別使用 concurrency::unbounded_buffer 限制應用程式所使用的執行緒數目的物件。此代理程式會使用固定數目的語彙基元值來初始化緩衝區。針對每個下載作業,此代理程式會在作業啟動之前從緩衝區中讀取語彙基元值,然後在作業完成之後將此值重新寫入緩衝區。當緩衝區是空的時,此代理程式就會等候其中一個下載作業將值重新寫入緩衝區。
下列範例會將同時工作的數目限制為可用硬體執行緒數目的兩倍。當您實驗過度訂閱時,這個值是很好的起點。您可以使用符合特定處理環境的值,也可以用動態方式變更此值,以便回應實際的工作負載。
// download-oversubscription.cpp
// compile with: /EHsc /MD /D "_AFXDLL"
#define _WIN32_WINNT 0x0501
#include <afxinet.h>
#include <concrtrm.h>
#include <agents.h>
#include <ppl.h>
#include <sstream>
#include <iostream>
#include <array>
using namespace concurrency;
using namespace std;
// Calls the provided work function and returns the number of milliseconds
// that it takes to call that function.
template <class Function>
__int64 time_call(Function&& f)
{
__int64 begin = GetTickCount();
f();
return GetTickCount() - begin;
}
// Downloads the file at the given URL.
CString GetHttpFile(CInternetSession& session, const CString& strUrl);
// Reads files from HTTP servers.
class http_reader : public agent
{
public:
explicit http_reader(CInternetSession& session,
ISource<string>& source,
unsigned int& total_bytes,
unsigned int max_concurrent_reads)
: _session(session)
, _source(source)
, _total_bytes(total_bytes)
{
// Add one token to the available tasks buffer for each
// possible concurrent read operation. The value of each token
// is not important, but can be useful for debugging.
for (unsigned int i = 0; i < max_concurrent_reads; ++i)
send(_available_tasks, i);
}
// Signals to the agent that there are no more items to download.
static const string input_sentinel;
protected:
void run()
{
// A task group. Each task in the group downloads one file.
task_group tasks;
// Holds the total number of bytes downloaded.
combinable<unsigned int> total_bytes;
// Read from the source buffer until the application
// sends the sentinel value.
string url;
while ((url = receive(_source)) != input_sentinel)
{
// Wait for a task to release an available slot.
unsigned int token = receive(_available_tasks);
// Create a task to download the file.
tasks.run([&, token, url] {
// Print a message.
wstringstream ss;
ss << L"Downloading " << url.c_str() << L"..." << endl;
wcout << ss.str();
// Download the file.
string content = download(url);
// Update the total number of bytes downloaded.
total_bytes.local() += content.size();
// Release the slot for another task.
send(_available_tasks, token);
});
}
// Wait for all tasks to finish.
tasks.wait();
// Compute the total number of bytes download on all threads.
_total_bytes = total_bytes.combine(plus<unsigned int>());
// Set the status of the agent to agent_done.
done();
}
// Downloads the file at the given URL.
string download(const string& url)
{
// Enable oversubscription.
Context::Oversubscribe(true);
// Download the file.
string content = GetHttpFile(_session, url.c_str());
// Disable oversubscription.
Context::Oversubscribe(false);
return content;
}
private:
// Manages the network connection.
CInternetSession& _session;
// A message buffer that holds the URL names to download.
ISource<string>& _source;
// The total number of bytes downloaded
unsigned int& _total_bytes;
// Limits the agent to a given number of simultaneous tasks.
unbounded_buffer<unsigned int> _available_tasks;
};
const string http_reader::input_sentinel("");
int wmain()
{
// Create an array of URL names to download.
// A real-world application might read the names from user input.
array<string, 21> urls = {
"http://www.adatum.com/",
"https://www.adventure-works.com/",
"http://www.alpineskihouse.com/",
"http://www.cpandl.com/",
"http://www.cohovineyard.com/",
"http://www.cohowinery.com/",
"http://www.cohovineyardandwinery.com/",
"https://www.contoso.com/",
"http://www.consolidatedmessenger.com/",
"http://www.fabrikam.com/",
"https://www.fourthcoffee.com/",
"http://www.graphicdesigninstitute.com/",
"http://www.humongousinsurance.com/",
"http://www.litwareinc.com/",
"http://www.lucernepublishing.com/",
"http://www.margiestravel.com/",
"http://www.northwindtraders.com/",
"https://www.proseware.com/",
"http://www.fineartschool.net",
"http://www.tailspintoys.com/",
http_reader::input_sentinel,
};
// Manages the network connection.
CInternetSession session("Microsoft Internet Browser");
// A message buffer that enables the application to send URL names to the
// agent.
unbounded_buffer<string> source_urls;
// The total number of bytes that the agent has downloaded.
unsigned int total_bytes = 0u;
// Create an http_reader object that can oversubscribe each processor by one.
http_reader reader(session, source_urls, total_bytes, 2*GetProcessorCount());
// Compute the amount of time that it takes for the agent to download all files.
__int64 elapsed = time_call([&] {
// Start the agent.
reader.start();
// Use the message buffer to send each URL name to the agent.
for_each(begin(urls), end(urls), [&](const string& url) {
send(source_urls, url);
});
// Wait for the agent to finish downloading.
agent::wait(&reader);
});
// Print the results.
wcout << L"Downloaded " << total_bytes
<< L" bytes in " << elapsed << " ms." << endl;
}
// Downloads the file at the given URL and returns the size of that file.
CString GetHttpFile(CInternetSession& session, const CString& strUrl)
{
CString strResult;
// Reads data from an HTTP server.
CHttpFile* pHttpFile = NULL;
try
{
// Open URL.
pHttpFile = (CHttpFile*)session.OpenURL(strUrl, 1,
INTERNET_FLAG_TRANSFER_ASCII |
INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE);
// Read the file.
if(pHttpFile != NULL)
{
UINT uiBytesRead;
do
{
char chBuffer[10000];
uiBytesRead = pHttpFile->Read(chBuffer, sizeof(chBuffer));
strResult += chBuffer;
}
while (uiBytesRead > 0);
}
}
catch (CInternetException)
{
// TODO: Handle exception
}
// Clean up and return.
delete pHttpFile;
return strResult;
}
這個範例會在配備四個處理器的電腦上產生下列輸出:
Downloading http://www.adatum.com/...
Downloading https://www.adventure-works.com/...
Downloading http://www.alpineskihouse.com/...
Downloading http://www.cpandl.com/...
Downloading http://www.cohovineyard.com/...
Downloading http://www.cohowinery.com/...
Downloading http://www.cohovineyardandwinery.com/...
Downloading https://www.contoso.com/...
Downloading http://www.consolidatedmessenger.com/...
Downloading http://www.fabrikam.com/...
Downloading https://www.fourthcoffee.com/...
Downloading http://www.graphicdesigninstitute.com/...
Downloading http://www.humongousinsurance.com/...
Downloading http://www.litwareinc.com/...
Downloading http://www.lucernepublishing.com/...
Downloading http://www.margiestravel.com/...
Downloading http://www.northwindtraders.com/...
Downloading https://www.proseware.com/...
Downloading http://www.fineartschool.net...
Downloading http://www.tailspintoys.com/...
Downloaded 1801040 bytes in 3276 ms.
啟用過度訂閱時,此範例的執行速度可能會更快,因為某些工作會執行,而其他工作則等候潛伏作業完成。
編譯程式碼
將範例程式碼複製並貼上它在 Visual Studio 專案中,或將它貼在檔名為下載 oversubscription.cpp 和 Visual Studio 的命令提示字元] 視窗中的下列其中一個執行命令然後。
cl.exe /EHsc /MD /D "_AFXDLL" download-oversubscription.cpp
cl.exe /EHsc /MT download-oversubscription.cpp
穩固程式設計
當您不再需要過度訂閱之後,請務必停用它。假設有一個函式,但是它無法處理另一個函式所擲回的例外狀況。如果您沒有在該函式傳回之前停用過度訂閱,任何其他平行工作也會過度訂閱目前的內容。
您可以使用「資源擷取為初始設定」(Resource Acquisition Is Initialization,RAII) 模式,將過度訂閱限制為指定的範圍。在 RAII 模式下,資料結構會配置於堆疊上。該資料結構會在建立時初始化或擷取資源,並在資料結構終結時終結或釋放該資源。RAII 模式可保證在封閉範圍結束之前呼叫解構函式。因此,當有例外狀況擲回或是函式包含多個 return 陳述式時,都會正確地管理資源。
下列範例會定義名為 scoped_blocking_signal 的結構。scoped_blocking_signal 結構的建構函式會啟用過度訂閱,而解構函式則會停用過度訂閱。
struct scoped_blocking_signal
{
scoped_blocking_signal()
{
concurrency::Context::Oversubscribe(true);
}
~scoped_blocking_signal()
{
concurrency::Context::Oversubscribe(false);
}
};
下列範例會將 download 方法的主體修改成使用 RAII,以便確保函式傳回之前停用過度訂閱。這項技術可確保 download 方法無例外狀況之虞。
// Downloads the file at the given URL.
string download(const string& url)
{
scoped_blocking_signal signal;
// Download the file.
return string(GetHttpFile(_session, url.c_str()));
}