Güvenlik kuyruğundaki monitör nesnesinden katkıda hata düzeltmesi
Son makalemde “Yazılım Mimarisi Modelleri: Nesne Monitörü” Güvenli bir kuyruk uyguladım. İki ciddi hata yaptım. Affedersin. Bugün bu hataları çözeceğim.
Rainer Grimm yıllardır yazılım mimarı, ekip ve eğitim müdürü olarak çalıştı. C ++ programlama dilleri, Python ve Haskell hakkında makaleler yazmayı seviyor, ancak uzman konferanslarla konuşmayı da seviyor. Modern C ++ blogunda, C ++ tutkusuyla yoğun bir şekilde ilgileniyor.
Bağlantıyı anlamak için önce son yazımdan yanlış uygulamayı sunmak istiyorum.
// monitorObject.cpp
#include <condition_variable>
#include <functional>
#include <queue>
#include <iostream>
#include <mutex>
#include <random>
#include <thread>
class Monitor {
public:
void lock() const {
monitMutex.lock();
}
void unlock() const {
monitMutex.unlock();
}
void notify_one() const noexcept {
monitCond.notify_one();
}
template <typename Predicate>
void wait(Predicate pred) const { // (10)
std::unique_lock<std::mutex> monitLock(monitMutex);
monitCond.wait(monitLock, pred);
}
private:
mutable std::mutex monitMutex;
mutable std::condition_variable monitCond;
};
template <typename T> // (1)
class ThreadSafeQueue: public Monitor {
public:
void add(T val){
lock();
myQueue.push(val); // (6)
unlock();
notify_one();
}
T get(){
wait( [this] { return ! myQueue.empty(); } ); // (2)
lock();
auto val = myQueue.front(); // (4)
myQueue.pop(); // (5)
unlock();
return val;
}
private:
std::queue<T> myQueue; // (3)
};
class Dice {
public:
int operator()(){ return rand(); }
private:
std::function<int()> rand = std::bind(std::uniform_int_distribution<>(1, 6),
std::default_random_engine());
};
int main(){
std::cout << 'n';
constexpr auto NumberThreads = 100;
ThreadSafeQueue<int> safeQueue; // (7)
auto addLambda = [&safeQueue](int val){ safeQueue.add(val); // (8)
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.get(); }; // (9)
std::vector<std::thread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr = std::thread(addLambda, dice());
std::vector<std::thread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::thread(getLambda);
for (auto& thr: addThreads) thr.join();
for (auto& thr: getThreads) thr.join();
std::cout << "nn";
}
Örneğin ana fikri, monitör nesnesinin bir sınıfta kapsüllenmesi ve bu nedenle yeniden kullanılabilmesidir. Sınıf Monitor Kullanın std::mutex Monitörün bir bloğu ve bir std::condition_variable bir monitör koşulu olarak. Sınıf Monitor Bir monitörün desteklemesi gereken minimum arayüzü sunar.
ThreadSafeQueue (1) genişletilmiş std::queue İplik güvenli bir arayüzün etrafında. ThreadSafeQueue Sınıf tarafından yönetiliyor Monitor Senkronize üyelerin işlevleri için üyelerinin işlevlerini kapalı ve kullanın add VE get Desteklemek için. Üye çalışıyor add VE get Monitör nesnesini korumak için monitör bloğunu kullanın. Bu özellikle iplik dışı sicuro için geçerlidir myQueue. add Yeni bir unsur olduğunda iş parçacığını bekletin myQueue Eklendi. Bu bildirim güvenlidir. Üye işlevi get (3) Daha fazla ilgiyi hak ediyor. Önce wait-Dağındaki durumun değişkenine kadar. Bu wait-Caccification, sizi kayıp ve özelliklerden korumak için ek bir yüklem gerektirir (C ++ Yönergeleri: Koşulların değişkenlerinin tehlikelerinin farkında olun). Dosyayı değiştirmek için işlemler myQueue (4) ve (5) de korunmalıdır çünkü bunlar myQueue.push(val) (6) Çakışabilir. Monitör nesnesi safeQueue (7) (8) ve (9) 'deki lambda işlevlerini senkronize olan bir sayıya kullanın safeQueue Ekle veya kaldırın. ThreadSafeQueue Aynı zamanda bir sınıf sınıfıdır ve her türlü değerleri emebilir. Yüz müşteri safeQueue 1 ila 6 arasında 100 rastgele sayı (satır 7), yüz kişi bu 100 sayıdan safeQueue kaldırmak. Programın programı konu numaralarını ve kimliklerini gösterir.
Bu programın iki ciddi sorunu var. Dietmar Kühl VE Frank Birdbacher Sorunları bir ve -mail olarak tanımladılar. İşte sözlerin. Yorumlarım italik ve yağ karakterinde.
struct Monitor {
using Lock = std::unique_lock<std::mutex>; // could be wrapper if you prefer
[[nodiscard]] Lock receiveGuard() { return Lock(monitMutex); }
template <typename Predicate>
void wait(Lock& kerberos, Predicate pred) { monitCond.wait(kerberos, pred); }
// …
};
template <typename T>
T ThreadSafeQueue<T>::get() {
auto kerberos = receiveGuard();
wait(kerberos, [this]{ return not myQueue.empty(); });
T rc = std::move(myQueue.front());
myqueue.pop();
return rc;
}
Bu sürüm istisna sorununu düzeltir get(). İçin add() Monitör nesnesini bir tane ile yapabilirsiniz lock_guard Kullanmak:
template <typename T>
void add(T val) {
{
std::lock_guard<Monitor> kerberos(*this);
myqueue.push(std::move(val));
}
notify_one();
}
Muhtemelen bildirimi bildirin “SendGuard“Bunu paketle lock_guard ve bir referans condition_variable Yıkımda bir bildirim içerir ve gönderir:
class SendGuard {
friend class Monitor;
using deleter = decltype([](auto& cond){ cond->notify_one(); });
std::unique_ptr<std::condition_variable, deleter> notifier;
std::lock_guard<std::mutex> kerberos;
SendGuard(auto& mutex, auto& cond): notifier(&cond), kerberos(mutex) {}
};
Hareketin hareketi ve muhrip hala public Tüm arayüzü temsil edin ve temsil eder! Bu kullanım olurdu add() Çok daha kolay:
template <typename T>
void add(T val) {
auto kerberos = sendGuard();
myqueue.push(val);
}
Sonuçta, Dietmar'ın tam uygulaması burada. Sayılar benimdeki sayılara karşılık gelir monitorObjec.cpp Örnek.
// monitorObject.cpp
#include <condition_variable>
#include <functional>
#include <queue>
#include <iostream>
#include <mutex>
#include <random>
#include <thread>
class Monitor {
public:
using Lock = std::unique_lock<std::mutex>;
[[nodiscard]] Lock receiveGuard() {
return Lock(monitMutex);
}
template <typename Predicate>
void wait(Lock& kerberos, Predicate pred) {
monitCond.wait(kerberos, pred);
}
class SendGuard {
friend class Monitor;
using deleter = decltype([](auto* cond){ cond->notify_one(); });
std::unique_ptr<std::condition_variable, deleter> notifier;
std::lock_guard<std::mutex> kerberos;
SendGuard(auto& mutex, auto& cond): notifier(&cond), kerberos(mutex) {}
};
SendGuard sendGuard() { return {monitMutex, monitCond}; }
private:
mutable std::mutex monitMutex;
mutable std::condition_variable monitCond;
};
template <typename T> // (1)
class ThreadSafeQueue: public Monitor {
public:
void add(T val){
auto kerberos = sendGuard();
myQueue.push(val); // (6)
}
T get(){
auto kerberos = receiveGuard();
wait(kerberos, [this] { return ! myQueue.empty(); } ); // (2)
auto val = myQueue.front(); // (4)
myQueue.pop(); // (5)
return val;
}
private:
std::queue<T> myQueue; // (3)
};
class Dice {
public:
int operator()(){ return rand(); }
private:
std::function<int()> rand = std::bind(std::uniform_int_distribution<>(1, 6),
std::default_random_engine());
};
int main(){
std::cout << 'n';
constexpr auto NumberThreads = 100;
ThreadSafeQueue<int> safeQueue; // (7)
auto addLambda = [&safeQueue](int val){ safeQueue.add(val); // (8)
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.get(); }; // (9)
std::vector<std::thread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr = std::thread(addLambda, dice());
std::vector<std::thread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::thread(getLambda);
for (auto& thr: addThreads) thr.join();
for (auto& thr: getThreads) thr.join();
std::cout << "nn";
}
Yukarıdaki tartışmanın bir sonucu olarak, Frank aşağıdaki sürüme sahiptir ve Monitör için kullanabileceği tutarlı ve kolay bir arayüze sahiptir.
// threadSafeQueue.cpp
#ifndef INCLUDED_PATTERNS_MONITOR2_MONITOR_HPP
#define INCLUDED_PATTERNS_MONITOR2_MONITOR_HPP
#include <atomic>
#include <algorithm>
#include <condition_variable>
#include <deque>
#include <iterator>
#include <mutex>
#include <stdexcept>
#include <thread>
#include <vector>
class Monitor {
public:
struct UnlockAndNotify {
std::mutex d_mutex;
std::condition_variable d_condition;
void lock() { d_mutex.lock(); }
void unlock() { d_mutex.unlock(); d_condition.notify_one(); }
};
private:
UnlockAndNotify d_combined;
public:
std::unique_lock<UnlockAndNotify> makeLockWithNotify() {
return std::unique_lock{d_combined};
}
template <typename PRED>
std::unique_lock<std::mutex> makeLockWithWait(PRED waitForCondition) {
std::unique_lock lock{d_combined.d_mutex};
d_combined.d_condition.wait(lock, waitForCondition);
return lock;
}
};
class ThreadQueue {
Monitor d_monitor;
std::deque<int> d_numberQueue;
auto makeLockWhenNotEmpty() {
return d_monitor.makeLockWithWait([this] { return !d_numberQueue.empty(); });
}
public:
void addNumber(int number) {
const auto lock = d_monitor.makeLockWithNotify();
d_numberQueue.push_back(number);
}
int removeNumber() {
const auto lock = makeLockWhenNotEmpty();
const auto number = d_numberQueue.front();
d_numberQueue.pop_front();
return number;
}
};
#endif
int main() {
ThreadQueue queue;
std::atomic<int> sharedSum{};
std::atomic<int> sharedCounter{};
std::vector<std::jthread> threads;
threads.reserve(200);
std::generate_n(std::back_inserter(threads), 100, [&] {
return std::jthread{[&] { sharedSum += queue.removeNumber(); }};
});
std::generate_n(std::back_inserter(threads), 100, [&] {
return std::jthread{[&] { queue.addNumber(++sharedCounter); }};
});
threads.clear(); // wait for all threads to finish
if (sharedSum.load() != 5050) {
throw std::logic_error("Wrong result for sum of 1..100");
}
}
Monitör nesnesinin uygulanması esnekliğine dayanmaktadır. std::unique_lock model parametresi aracılığıyla. C ++ standartlarının tüm kilitleri, her sınıfta kullanılabilir. lock()– VE unlock()-yöntemler. Sınıf UnlockAndNotify Bu arayüzü uygulayın ve durum durumunu şurada ayarlayın unlock()-Ücretsiz yöntem. Buna ek olarak, sınıf teklifleri Monitor A tarafından bildirimsiz olmak üzere, biri ve diğeri olmak üzere iki farklı blok türü oluşturmanın mümkün olduğu azaltılmış bir kamu arayüzü std::unique_lock Veya parkurda hepsi UnlockAndNotify-Prinde veya sadece ne içerdiğine std::mutex Yaratılmıştır.
Aralarında seçim yaparken std::unique_lock VE std::lock_guard Tercih ederim (Açık) unique_lock arayüzde. Bu seçim, monitör sınıfının ana esnekliğinin kullanıcısına izin verir. Bu esnekliği, performansdaki olası bir farktan daha yüksek takdir ediyorum lock_guardBu yine de ölçülmelidir. Örnek verilerinin bu esneklikten yararlanmadığını itiraf ediyorum.
Daha sonra, Dietmar Frank fikri geliştirildi: Burada korunan veriler monitörde korunur, bu da korunmasız erişmeyi zorlaştırır.
// threadsafequeue2.cpp
#ifndef INCLUDED_PATTERNS_MONITOR3_MONITOR_HPP
#define INCLUDED_PATTERNS_MONITOR3_MONITOR_HPP
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <iostream>
#include <iterator>
#include <mutex>
#include <random>
#include <stdexcept>
#include <thread>
#include <tuple>
#include <vector>
namespace patterns::monitor3 {
template <typename T>
class Monitor {
public:
struct UnlockAndNotify {
std::mutex d_mutex;
std::condition_variable d_condition;
void lock() { d_mutex.lock(); }
void unlock() { d_mutex.unlock(); d_condition.notify_one(); }
};
private:
mutable UnlockAndNotify d_combined;
mutable T d_data;
public:
std::tuple<T&, std::unique_lock<UnlockAndNotify>> makeProducerLock() const {
return { d_data, std::unique_lock{d_combined} };
}
template <typename PRED>
std::tuple<T&, std::unique_lock<std::mutex>> makeConsumerLockWhen(PRED predicate) const {
std::unique_lock lock{d_combined.d_mutex};
d_combined.d_condition.wait(lock, [this, predicate]{ return predicate(d_data); });
return { d_data, std::move(lock) };
}
};
template <typename T>
class ThreadQueue {
Monitor<std::deque<T>> d_monitor;
public:
void add(T number) {
auto[numberQueue, lock] = d_monitor.makeProducerLock();
numberQueue.push_back(number);
}
T remove() {
auto[numberQueue, lock] = d_monitor.makeConsumerLockWhen([](auto& numberQueue) { return !numberQueue.empty(); });
const auto number = numberQueue.front();
numberQueue.pop_front();
return number;
}
};
}
#endif
class Dice {
public:
int operator()(){ return rand(); }
private:
std::function<int()> rand = std::bind(std::uniform_int_distribution<>(1, 6),
std::default_random_engine());
};
int main(){
std::cout << 'n';
constexpr auto NumberThreads = 100;
patterns::monitor3::ThreadQueue<int> safeQueue;
auto addLambda = [&safeQueue](int val){ safeQueue.add(val);
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.remove(); };
std::vector<std::thread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr = std::thread(addLambda, dice());
std::vector<std::thread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::thread(getLambda);
for (auto& thr: addThreads) thr.join();
for (auto& thr: getThreads) thr.join();
std::cout << "nn";
}
Tekrar teşekkürler Açık VE Diyetmar. Son makalemde güvenli bir iplik kuyruğunun kusurlu uygulamasıyla, rekabetin çok zorlu olduğunu göstermek istemedim. Muteks'i bir bloğa koymamış olması özellikle can sıkıcıdır (hata 2). C ++ kurslarımda öğretiyorum: NNM (çıplak muteks yok).
Sırada ne var?
Bir sonraki makalemde kendimi C ++ 20: C ++ 23'ün geleceğine derinlemesine daldıracağım.
(RME)
Son makalemde “Yazılım Mimarisi Modelleri: Nesne Monitörü” Güvenli bir kuyruk uyguladım. İki ciddi hata yaptım. Affedersin. Bugün bu hataları çözeceğim.

Rainer Grimm yıllardır yazılım mimarı, ekip ve eğitim müdürü olarak çalıştı. C ++ programlama dilleri, Python ve Haskell hakkında makaleler yazmayı seviyor, ancak uzman konferanslarla konuşmayı da seviyor. Modern C ++ blogunda, C ++ tutkusuyla yoğun bir şekilde ilgileniyor.

Bağlantıyı anlamak için önce son yazımdan yanlış uygulamayı sunmak istiyorum.
// monitorObject.cpp
#include <condition_variable>
#include <functional>
#include <queue>
#include <iostream>
#include <mutex>
#include <random>
#include <thread>
class Monitor {
public:
void lock() const {
monitMutex.lock();
}
void unlock() const {
monitMutex.unlock();
}
void notify_one() const noexcept {
monitCond.notify_one();
}
template <typename Predicate>
void wait(Predicate pred) const { // (10)
std::unique_lock<std::mutex> monitLock(monitMutex);
monitCond.wait(monitLock, pred);
}
private:
mutable std::mutex monitMutex;
mutable std::condition_variable monitCond;
};
template <typename T> // (1)
class ThreadSafeQueue: public Monitor {
public:
void add(T val){
lock();
myQueue.push(val); // (6)
unlock();
notify_one();
}
T get(){
wait( [this] { return ! myQueue.empty(); } ); // (2)
lock();
auto val = myQueue.front(); // (4)
myQueue.pop(); // (5)
unlock();
return val;
}
private:
std::queue<T> myQueue; // (3)
};
class Dice {
public:
int operator()(){ return rand(); }
private:
std::function<int()> rand = std::bind(std::uniform_int_distribution<>(1, 6),
std::default_random_engine());
};
int main(){
std::cout << 'n';
constexpr auto NumberThreads = 100;
ThreadSafeQueue<int> safeQueue; // (7)
auto addLambda = [&safeQueue](int val){ safeQueue.add(val); // (8)
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.get(); }; // (9)
std::vector<std::thread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr = std::thread(addLambda, dice());
std::vector<std::thread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::thread(getLambda);
for (auto& thr: addThreads) thr.join();
for (auto& thr: getThreads) thr.join();
std::cout << "nn";
}
Örneğin ana fikri, monitör nesnesinin bir sınıfta kapsüllenmesi ve bu nedenle yeniden kullanılabilmesidir. Sınıf Monitor Kullanın std::mutex Monitörün bir bloğu ve bir std::condition_variable bir monitör koşulu olarak. Sınıf Monitor Bir monitörün desteklemesi gereken minimum arayüzü sunar.
ThreadSafeQueue (1) genişletilmiş std::queue İplik güvenli bir arayüzün etrafında. ThreadSafeQueue Sınıf tarafından yönetiliyor Monitor Senkronize üyelerin işlevleri için üyelerinin işlevlerini kapalı ve kullanın add VE get Desteklemek için. Üye çalışıyor add VE get Monitör nesnesini korumak için monitör bloğunu kullanın. Bu özellikle iplik dışı sicuro için geçerlidir myQueue. add Yeni bir unsur olduğunda iş parçacığını bekletin myQueue Eklendi. Bu bildirim güvenlidir. Üye işlevi get (3) Daha fazla ilgiyi hak ediyor. Önce wait-Dağındaki durumun değişkenine kadar. Bu wait-Caccification, sizi kayıp ve özelliklerden korumak için ek bir yüklem gerektirir (C ++ Yönergeleri: Koşulların değişkenlerinin tehlikelerinin farkında olun). Dosyayı değiştirmek için işlemler myQueue (4) ve (5) de korunmalıdır çünkü bunlar myQueue.push(val) (6) Çakışabilir. Monitör nesnesi safeQueue (7) (8) ve (9) 'deki lambda işlevlerini senkronize olan bir sayıya kullanın safeQueue Ekle veya kaldırın. ThreadSafeQueue Aynı zamanda bir sınıf sınıfıdır ve her türlü değerleri emebilir. Yüz müşteri safeQueue 1 ila 6 arasında 100 rastgele sayı (satır 7), yüz kişi bu 100 sayıdan safeQueue kaldırmak. Programın programı konu numaralarını ve kimliklerini gösterir.

Bu programın iki ciddi sorunu var. Dietmar Kühl VE Frank Birdbacher Sorunları bir ve -mail olarak tanımladılar. İşte sözlerin. Yorumlarım italik ve yağ karakterinde.
- İçinde ThreadSafeQueue::get() Yolla olacak Monitor::wait() Test Edilir myQueue Bir öğe bir eleman içerir veya bekler. Ancak, blok sadece içeride wait() tutuldu, içinde get() Elemanın hala içeride olduğundan emin olamıyorum myQueue IS: başka bir iş parçacığı bloğu alabilir ve elemanı kaldırabilir, bu da myqueue.front () olarak adlandırıldığında belirsiz davranışa yol açar.
- Eğer kopya/yer değiştirme üreticisi T bir istisna başlatır ThreadSafeQueue Tutarsız bir durumda: Hiçbir üye işlevi aktif değildir, ancak muteks çekilir.
struct Monitor {
using Lock = std::unique_lock<std::mutex>; // could be wrapper if you prefer
[[nodiscard]] Lock receiveGuard() { return Lock(monitMutex); }
template <typename Predicate>
void wait(Lock& kerberos, Predicate pred) { monitCond.wait(kerberos, pred); }
// …
};
template <typename T>
T ThreadSafeQueue<T>::get() {
auto kerberos = receiveGuard();
wait(kerberos, [this]{ return not myQueue.empty(); });
T rc = std::move(myQueue.front());
myqueue.pop();
return rc;
}
Bu sürüm istisna sorununu düzeltir get(). İçin add() Monitör nesnesini bir tane ile yapabilirsiniz lock_guard Kullanmak:
template <typename T>
void add(T val) {
{
std::lock_guard<Monitor> kerberos(*this);
myqueue.push(std::move(val));
}
notify_one();
}
Muhtemelen bildirimi bildirin “SendGuard“Bunu paketle lock_guard ve bir referans condition_variable Yıkımda bir bildirim içerir ve gönderir:
class SendGuard {
friend class Monitor;
using deleter = decltype([](auto& cond){ cond->notify_one(); });
std::unique_ptr<std::condition_variable, deleter> notifier;
std::lock_guard<std::mutex> kerberos;
SendGuard(auto& mutex, auto& cond): notifier(&cond), kerberos(mutex) {}
};
Hareketin hareketi ve muhrip hala public Tüm arayüzü temsil edin ve temsil eder! Bu kullanım olurdu add() Çok daha kolay:
template <typename T>
void add(T val) {
auto kerberos = sendGuard();
myqueue.push(val);
}
Sonuçta, Dietmar'ın tam uygulaması burada. Sayılar benimdeki sayılara karşılık gelir monitorObjec.cpp Örnek.
// monitorObject.cpp
#include <condition_variable>
#include <functional>
#include <queue>
#include <iostream>
#include <mutex>
#include <random>
#include <thread>
class Monitor {
public:
using Lock = std::unique_lock<std::mutex>;
[[nodiscard]] Lock receiveGuard() {
return Lock(monitMutex);
}
template <typename Predicate>
void wait(Lock& kerberos, Predicate pred) {
monitCond.wait(kerberos, pred);
}
class SendGuard {
friend class Monitor;
using deleter = decltype([](auto* cond){ cond->notify_one(); });
std::unique_ptr<std::condition_variable, deleter> notifier;
std::lock_guard<std::mutex> kerberos;
SendGuard(auto& mutex, auto& cond): notifier(&cond), kerberos(mutex) {}
};
SendGuard sendGuard() { return {monitMutex, monitCond}; }
private:
mutable std::mutex monitMutex;
mutable std::condition_variable monitCond;
};
template <typename T> // (1)
class ThreadSafeQueue: public Monitor {
public:
void add(T val){
auto kerberos = sendGuard();
myQueue.push(val); // (6)
}
T get(){
auto kerberos = receiveGuard();
wait(kerberos, [this] { return ! myQueue.empty(); } ); // (2)
auto val = myQueue.front(); // (4)
myQueue.pop(); // (5)
return val;
}
private:
std::queue<T> myQueue; // (3)
};
class Dice {
public:
int operator()(){ return rand(); }
private:
std::function<int()> rand = std::bind(std::uniform_int_distribution<>(1, 6),
std::default_random_engine());
};
int main(){
std::cout << 'n';
constexpr auto NumberThreads = 100;
ThreadSafeQueue<int> safeQueue; // (7)
auto addLambda = [&safeQueue](int val){ safeQueue.add(val); // (8)
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.get(); }; // (9)
std::vector<std::thread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr = std::thread(addLambda, dice());
std::vector<std::thread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::thread(getLambda);
for (auto& thr: addThreads) thr.join();
for (auto& thr: getThreads) thr.join();
std::cout << "nn";
}
Yukarıdaki tartışmanın bir sonucu olarak, Frank aşağıdaki sürüme sahiptir ve Monitör için kullanabileceği tutarlı ve kolay bir arayüze sahiptir.
// threadSafeQueue.cpp
#ifndef INCLUDED_PATTERNS_MONITOR2_MONITOR_HPP
#define INCLUDED_PATTERNS_MONITOR2_MONITOR_HPP
#include <atomic>
#include <algorithm>
#include <condition_variable>
#include <deque>
#include <iterator>
#include <mutex>
#include <stdexcept>
#include <thread>
#include <vector>
class Monitor {
public:
struct UnlockAndNotify {
std::mutex d_mutex;
std::condition_variable d_condition;
void lock() { d_mutex.lock(); }
void unlock() { d_mutex.unlock(); d_condition.notify_one(); }
};
private:
UnlockAndNotify d_combined;
public:
std::unique_lock<UnlockAndNotify> makeLockWithNotify() {
return std::unique_lock{d_combined};
}
template <typename PRED>
std::unique_lock<std::mutex> makeLockWithWait(PRED waitForCondition) {
std::unique_lock lock{d_combined.d_mutex};
d_combined.d_condition.wait(lock, waitForCondition);
return lock;
}
};
class ThreadQueue {
Monitor d_monitor;
std::deque<int> d_numberQueue;
auto makeLockWhenNotEmpty() {
return d_monitor.makeLockWithWait([this] { return !d_numberQueue.empty(); });
}
public:
void addNumber(int number) {
const auto lock = d_monitor.makeLockWithNotify();
d_numberQueue.push_back(number);
}
int removeNumber() {
const auto lock = makeLockWhenNotEmpty();
const auto number = d_numberQueue.front();
d_numberQueue.pop_front();
return number;
}
};
#endif
int main() {
ThreadQueue queue;
std::atomic<int> sharedSum{};
std::atomic<int> sharedCounter{};
std::vector<std::jthread> threads;
threads.reserve(200);
std::generate_n(std::back_inserter(threads), 100, [&] {
return std::jthread{[&] { sharedSum += queue.removeNumber(); }};
});
std::generate_n(std::back_inserter(threads), 100, [&] {
return std::jthread{[&] { queue.addNumber(++sharedCounter); }};
});
threads.clear(); // wait for all threads to finish
if (sharedSum.load() != 5050) {
throw std::logic_error("Wrong result for sum of 1..100");
}
}
Monitör nesnesinin uygulanması esnekliğine dayanmaktadır. std::unique_lock model parametresi aracılığıyla. C ++ standartlarının tüm kilitleri, her sınıfta kullanılabilir. lock()– VE unlock()-yöntemler. Sınıf UnlockAndNotify Bu arayüzü uygulayın ve durum durumunu şurada ayarlayın unlock()-Ücretsiz yöntem. Buna ek olarak, sınıf teklifleri Monitor A tarafından bildirimsiz olmak üzere, biri ve diğeri olmak üzere iki farklı blok türü oluşturmanın mümkün olduğu azaltılmış bir kamu arayüzü std::unique_lock Veya parkurda hepsi UnlockAndNotify-Prinde veya sadece ne içerdiğine std::mutex Yaratılmıştır.
Aralarında seçim yaparken std::unique_lock VE std::lock_guard Tercih ederim (Açık) unique_lock arayüzde. Bu seçim, monitör sınıfının ana esnekliğinin kullanıcısına izin verir. Bu esnekliği, performansdaki olası bir farktan daha yüksek takdir ediyorum lock_guardBu yine de ölçülmelidir. Örnek verilerinin bu esneklikten yararlanmadığını itiraf ediyorum.
Daha sonra, Dietmar Frank fikri geliştirildi: Burada korunan veriler monitörde korunur, bu da korunmasız erişmeyi zorlaştırır.
// threadsafequeue2.cpp
#ifndef INCLUDED_PATTERNS_MONITOR3_MONITOR_HPP
#define INCLUDED_PATTERNS_MONITOR3_MONITOR_HPP
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <iostream>
#include <iterator>
#include <mutex>
#include <random>
#include <stdexcept>
#include <thread>
#include <tuple>
#include <vector>
namespace patterns::monitor3 {
template <typename T>
class Monitor {
public:
struct UnlockAndNotify {
std::mutex d_mutex;
std::condition_variable d_condition;
void lock() { d_mutex.lock(); }
void unlock() { d_mutex.unlock(); d_condition.notify_one(); }
};
private:
mutable UnlockAndNotify d_combined;
mutable T d_data;
public:
std::tuple<T&, std::unique_lock<UnlockAndNotify>> makeProducerLock() const {
return { d_data, std::unique_lock{d_combined} };
}
template <typename PRED>
std::tuple<T&, std::unique_lock<std::mutex>> makeConsumerLockWhen(PRED predicate) const {
std::unique_lock lock{d_combined.d_mutex};
d_combined.d_condition.wait(lock, [this, predicate]{ return predicate(d_data); });
return { d_data, std::move(lock) };
}
};
template <typename T>
class ThreadQueue {
Monitor<std::deque<T>> d_monitor;
public:
void add(T number) {
auto[numberQueue, lock] = d_monitor.makeProducerLock();
numberQueue.push_back(number);
}
T remove() {
auto[numberQueue, lock] = d_monitor.makeConsumerLockWhen([](auto& numberQueue) { return !numberQueue.empty(); });
const auto number = numberQueue.front();
numberQueue.pop_front();
return number;
}
};
}
#endif
class Dice {
public:
int operator()(){ return rand(); }
private:
std::function<int()> rand = std::bind(std::uniform_int_distribution<>(1, 6),
std::default_random_engine());
};
int main(){
std::cout << 'n';
constexpr auto NumberThreads = 100;
patterns::monitor3::ThreadQueue<int> safeQueue;
auto addLambda = [&safeQueue](int val){ safeQueue.add(val);
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.remove(); };
std::vector<std::thread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr = std::thread(addLambda, dice());
std::vector<std::thread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::thread(getLambda);
for (auto& thr: addThreads) thr.join();
for (auto& thr: getThreads) thr.join();
std::cout << "nn";
}
Tekrar teşekkürler Açık VE Diyetmar. Son makalemde güvenli bir iplik kuyruğunun kusurlu uygulamasıyla, rekabetin çok zorlu olduğunu göstermek istemedim. Muteks'i bir bloğa koymamış olması özellikle can sıkıcıdır (hata 2). C ++ kurslarımda öğretiyorum: NNM (çıplak muteks yok).
Sırada ne var?
Bir sonraki makalemde kendimi C ++ 20: C ++ 23'ün geleceğine derinlemesine daldıracağım.
(RME)