如何:使用內容類別實作合作式信號
本主題說明如何使用 concurrency::Context 類別來實作合作號誌類別。
備註
類別 Context
可讓您封鎖或產生目前的執行內容。 當目前內容無法繼續時,封鎖或產生目前的內容很有用,因為資源無法使用。 旗號是目前執行內容必須等候資源可供使用之一情況的範例。 像重要區段對象一樣,旗號是同步處理物件,可讓一個內容中的程式代碼具有資源的獨佔存取權。 不過,與重要區段物件不同,旗號可讓多個內容同時存取資源。 如果內容數目上限保留號誌鎖定,則每個額外的內容必須等候另一個內容釋放鎖定。
實作號誌類別
- 宣告名為
semaphore
的類別。 將和private
區段新增public
至此類別。
// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
private:
};
- 在 類別的 區
private
段中,宣告保存號誌計數的 std::atomic 變數,以及保存必須等候取得旗號的內容的並行::concurrent_queuesemaphore
物件。
// The semaphore count.
atomic<long long> _semaphore_count;
// A concurrency-safe queue of contexts that must wait to
// acquire the semaphore.
concurrent_queue<Context*> _waiting_contexts;
- 在 類別的
semaphore
區public
段中,實作 建構函式。 建構函式會接受值long long
,指定可以同時保存鎖定的內容數目上限。
explicit semaphore(long long capacity)
: _semaphore_count(capacity)
{
}
- 在 類別的
semaphore
區public
段中,實acquire
作 方法。 這個方法會將號誌計數遞減為不可部分完成的作業。 如果信號計數變成負數,請將目前的內容新增至等候佇列的結尾,並呼叫 concurrency::Context::Block 方法來封鎖目前的內容。
// Acquires access to the semaphore.
void acquire()
{
// The capacity of the semaphore is exceeded when the semaphore count
// falls below zero. When this happens, add the current context to the
// back of the wait queue and block the current context.
if (--_semaphore_count < 0)
{
_waiting_contexts.push(Context::CurrentContext());
Context::Block();
}
}
- 在 類別的
semaphore
區public
段中,實release
作 方法。 這個方法會將號誌計數遞增為不可部分完成的作業。 如果遞增作業前的號誌計數為負數,則至少有一個正在等候鎖定的內容。 在此情況下,請解除封鎖等候佇列前方的內容。
// Releases access to the semaphore.
void release()
{
// If the semaphore count is negative, unblock the first waiting context.
if (++_semaphore_count <= 0)
{
// A call to acquire might have decremented the counter, but has not
// yet finished adding the context to the queue.
// Create a spin loop that waits for the context to become available.
Context* waiting = NULL;
while (!_waiting_contexts.try_pop(waiting))
{
Context::Yield();
}
// Unblock the context.
waiting->Unblock();
}
}
範例
semaphore
此範例中的 類別會合作運作,因為 Context::Block
和 Context::Yield
方法會產生執行,讓運行時間可以執行其他工作。
acquire
方法會遞減計數器,但在另一個內容呼叫 release
方法之前,它可能不會完成將內容新增至等候佇列。 為了考慮到這一點, release
此方法會使用微調循環來呼叫 並行::Context::Yield 方法,以等候 acquire
方法完成新增內容。
方法release
可以在方法呼叫 方法之前acquire
呼叫 Context::Unblock
Context::Block
方法。 您不需要保護此競爭條件,因為運行時間允許依任何順序呼叫這些方法。 release
如果方法在方法呼叫相同內容之前acquire
呼叫 Context::Unblock
Context::Block
,該內容會保持解除封鎖。 運行時間只需要將 的每個呼叫 Context::Block
都與 對應的呼叫 Context::Unblock
相符。
下列範例顯示完整的 semaphore
類別。 函 wmain
式會顯示此類別的基本用法。 函 wmain
式會使用 concurrency::p arallel_for 演演算法來建立數個需要存取旗號的工作。 因為三個線程可以隨時保存鎖定,某些工作必須等候另一個工作完成並釋放鎖定。
// cooperative-semaphore.cpp
// compile with: /EHsc
#include <atomic>
#include <concrt.h>
#include <ppl.h>
#include <concurrent_queue.h>
#include <iostream>
#include <sstream>
using namespace concurrency;
using namespace std;
// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
explicit semaphore(long long capacity)
: _semaphore_count(capacity)
{
}
// Acquires access to the semaphore.
void acquire()
{
// The capacity of the semaphore is exceeded when the semaphore count
// falls below zero. When this happens, add the current context to the
// back of the wait queue and block the current context.
if (--_semaphore_count < 0)
{
_waiting_contexts.push(Context::CurrentContext());
Context::Block();
}
}
// Releases access to the semaphore.
void release()
{
// If the semaphore count is negative, unblock the first waiting context.
if (++_semaphore_count <= 0)
{
// A call to acquire might have decremented the counter, but has not
// yet finished adding the context to the queue.
// Create a spin loop that waits for the context to become available.
Context* waiting = NULL;
while (!_waiting_contexts.try_pop(waiting))
{
Context::Yield();
}
// Unblock the context.
waiting->Unblock();
}
}
private:
// The semaphore count.
atomic<long long> _semaphore_count;
// A concurrency-safe queue of contexts that must wait to
// acquire the semaphore.
concurrent_queue<Context*> _waiting_contexts;
};
int wmain()
{
// Create a semaphore that allows at most three threads to
// hold the lock.
semaphore s(3);
parallel_for(0, 10, [&](int i) {
// Acquire the lock.
s.acquire();
// Print a message to the console.
wstringstream ss;
ss << L"In loop iteration " << i << L"..." << endl;
wcout << ss.str();
// Simulate work by waiting for two seconds.
wait(2000);
// Release the lock.
s.release();
});
}
此範例會產生下列範例輸出。
In loop iteration 5...
In loop iteration 0...
In loop iteration 6...
In loop iteration 1...
In loop iteration 2...
In loop iteration 7...
In loop iteration 3...
In loop iteration 8...
In loop iteration 9...
In loop iteration 4...
如需 類別 concurrent_queue
的詳細資訊,請參閱 平行容器和物件。 如需演算法 parallel_for
的詳細資訊,請參閱 平行演算法。
編譯程式碼
複製範例程式代碼,並將其貼到 Visual Studio 專案中,或貼到名為 cooperative-semaphore.cpp
的檔案中,然後在 Visual Studio 命令提示字元視窗中執行下列命令。
cl.exe /EHsc cooperative-semaphore.cpp
穩固程式設計
您可以使用資源 擷取是初始化 (RAII) 模式,將物件的存取 semaphore
限制在指定的範圍。 在RAII模式下,會在堆疊上配置數據結構。 該數據結構會在建立資源時初始化或取得資源,並在數據結構終結時終結或釋放該資源。 RAII 模式保證解構函式會在封入範圍結束之前呼叫。 因此,當擲回例外狀況或函式包含多個 return
語句時,資源就會正確管理。
下列範例會定義名為 scoped_lock
的類別,該類別定義於 類別的 public
semaphore
區段中。 類別 scoped_lock
類似於 並行::critical_section::scoped_lock 和 並行::reader_writer_lock::scoped_lock 類別。 類別的 semaphore::scoped_lock
建構函式會取得指定 semaphore
物件的存取權,而解構函式會釋放該物件的存取權。
// An exception-safe RAII wrapper for the semaphore class.
class scoped_lock
{
public:
// Acquires access to the semaphore.
scoped_lock(semaphore& s)
: _s(s)
{
_s.acquire();
}
// Releases access to the semaphore.
~scoped_lock()
{
_s.release();
}
private:
semaphore& _s;
};
下列範例會修改傳遞至 parallel_for
演算法的工作函式主體,讓它使用RAII確保旗號在函式傳回之前釋放。 這項技術可確保工作函式是安全的例外狀況。
parallel_for(0, 10, [&](int i) {
// Create an exception-safe scoped_lock object that holds the lock
// for the duration of the current scope.
semaphore::scoped_lock auto_lock(s);
// Print a message to the console.
wstringstream ss;
ss << L"In loop iteration " << i << L"..." << endl;
wcout << ss.str();
// Simulate work by waiting for two seconds.
wait(2000);
});