Nasıl yapılır: Bağlam Sınıfını İşbirlikçi Semafor Uygulamak için Kullanma
Bu konu, işbirliğine dayalı semafor sınıfı uygulamak için concurrency::Context sınıfının nasıl kullanılacağını gösterir.
Açıklamalar
sınıfı, Context
geçerli yürütme bağlamını engellemenize veya vermenizi sağlar. Geçerli bağlamı engellemek veya oluşturmak, bir kaynak kullanılamadığından geçerli bağlam devam edemiyorsa yararlıdır. Semafor, geçerli yürütme bağlamının bir kaynağın kullanılabilir olmasını beklemesi gereken bir duruma örnektir. Kritik bölüm nesnesi gibi semafor, bir bağlamdaki kodun kaynağa özel erişime sahip olmasını sağlayan bir eşitleme nesnesidir. Ancak, kritik bir bölüm nesnesinin aksine, semafor birden fazla bağlamın kaynağa eşzamanlı olarak erişmesini sağlar. Bağlam sayısı üst sınırı semafor kilidi barındırıyorsa, her ek bağlamın kilidi serbest bırakmak için başka bir bağlam beklemesi gerekir.
Semafor sınıfını uygulamak için
- adlı
semaphore
bir sınıf bildirin.private
Ve bölümlerini bu sınıfa ekleyinpublic
.
// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
private:
};
private
sınıfının bölümündesemaphore
, semafor sayısını ve semaforu almak için beklemesi gereken bağlamları tutan bir eşzamanlılık::concurrent_queue nesnesini tutan bir std::atomic değişkeni bildirin.
// 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;
public
sınıfının bölümündesemaphore
oluşturucuyu uygulayın. Oluşturucu, kilidi eşzamanlı olarak tutabilecek bağlam sayısı üst sınırını belirten birlong long
değer alır.
explicit semaphore(long long capacity)
: _semaphore_count(capacity)
{
}
public
sınıfının bölümündesemaphore
yöntemini uygulayınacquire
. Bu yöntem semafor sayısını atomik işlem olarak azaltmaya çalışır. Semafor sayısı negatif olursa, bekleme kuyruğunun sonuna geçerli bağlamı ekleyin ve geçerli bağlamı engellemek için eşzamanlılık::Context::Block yöntemini çağırın.
// 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();
}
}
public
sınıfının bölümündesemaphore
yöntemini uygulayınrelease
. Bu yöntem semafor sayısını atomik işlem olarak artırır. Artış işleminden önce semafor sayısı negatifse, kilidi bekleyen en az bir bağlam vardır. Bu durumda bekleme kuyruğunun önündeki bağlamın engelini kaldırın.
// 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();
}
}
Örnek
Çalışma zamanının semaphore
diğer görevleri gerçekleştirebilmesi için Context::Block
ve Context::Yield
yöntemleri yürütmeyi sağladığından, bu örnekteki sınıf işbirliği içinde davranır.
acquire
yöntemi sayacını geri alır, ancak başka bir bağlam yöntemi çağırmadan release
önce bekleme kuyruğuna bağlam eklemeyi tamamlayameyebilir. Bunu hesaba katmak için yöntemi, yöntemin release
bağlam eklemeyi tamamlanmasını beklemek için acquire
eşzamanlılık::Context::Yield yöntemini çağıran bir döndürme döngüsü kullanır.
release
yöntemi yöntemini çağırmadan önce acquire
yöntemini çağırabilir Context::Unblock
Context::Block
. Çalışma zamanı bu yöntemlerin herhangi bir sırada çağrılmasına izin verdiğinden bu yarış durumuna karşı koruma yapmanız gerekmez. yöntemi aynı bağlamı release
çağırmadan önce acquire
yöntemini çağırırsa Context::Unblock
Context::Block
, bu bağlam engellenmemiş olarak kalır. Çalışma zamanı yalnızca her çağrısının Context::Block
öğesine karşılık gelen bir çağrıyla eşleşmesini Context::Unblock
gerektirir.
Aşağıdaki örnekte sınıfın tamamı semaphore
gösterilmektedir. işlevi bu wmain
sınıfın temel kullanımını gösterir. wmain
işlevi semafora erişim gerektiren çeşitli görevler oluşturmak için concurrency::p arallel_for algoritmasını kullanır. Kilidi her zaman üç iş parçacığı tutabildiğinden, bazı görevlerin başka bir görevin bitmesini ve kilidi serbest bırakmasını beklemesi gerekir.
// 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();
});
}
Bu örnek aşağıdaki örnek çıktıyı oluşturur.
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...
sınıfı hakkında concurrent_queue
daha fazla bilgi için bkz . Paralel Kapsayıcılar ve Nesneler. Algoritma hakkında parallel_for
daha fazla bilgi için bkz . Paralel Algoritmalar.
Kod Derleniyor
Örnek kodu kopyalayıp bir Visual Studio projesine yapıştırın veya adlı cooperative-semaphore.cpp
bir dosyaya yapıştırın ve ardından bir Visual Studio Komut İstemi penceresinde aşağıdaki komutu çalıştırın.
cl.exe /EHsc cooperative-semaphore.cpp
Güçlü Programlama
Bir nesneye erişimi belirli bir semaphore
kapsamla sınırlamak için Kaynak Alma Başlatmadır (RAII) desenini kullanabilirsiniz. RAII deseni altında, yığında bir veri yapısı ayrılır. Bu veri yapısı oluşturulduğunda bir kaynağı başlatır veya alır ve veri yapısı yok edildiğinde bu kaynağı yok eder veya serbest bırakır. RAII deseni, kapsayan kapsam çıkmadan önce yıkıcının çağrılmasını garanti eder. Bu nedenle, bir özel durum oluştuğunda veya bir işlev birden çok return
deyim içerdiğinde kaynak doğru şekilde yönetilir.
Aşağıdaki örnek, sınıfın bölümünde semaphore
tanımlanan public
adlı scoped_lock
bir sınıfı tanımlar. sınıfı scoped_lock
eşzamanlılık::critical_section::scoped_lock ve eşzamanlılık::reader_writer_lock::scoped_lock sınıflarına benzer. sınıfı oluşturucu, semaphore::scoped_lock
verilen semaphore
nesneye erişim alır ve yıkıcı bu nesneye erişimi serbest bırakır.
// 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;
};
Aşağıdaki örnek, semaforun işlev döndürülmeden önce serbest bırakılmasını sağlamak için parallel_for
RAII kullanması için algoritmaya geçirilen iş işlevinin gövdesini değiştirir. Bu teknik, iş işlevinin özel durum açısından güvenli olmasını sağlar.
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);
});